【JavaSE全面教学】Java集合框架上Day12(2026年)

发布时间:2026/5/19 6:21:02

【JavaSE全面教学】Java集合框架上Day12(2026年) 写在前面这是JavaSE系列的第12篇。集合框架是Java中最常用的工具之一面试必问。今天我们先讲List家族——ArrayList和LinkedList这两个是日常开发中使用频率最高的集合类。文章目录一、集合框架概述1.1 为什么需要集合1.2 集合框架体系二、ArrayList最常用的集合2.1 ArrayList的基本使用2.2 ArrayList的遍历2.3 ArrayList的底层原理面试重点2.4 ArrayList的性能分析2.5 ArrayList的常见坑三、LinkedList双向链表3.1 LinkedList的基本使用3.2 LinkedList的底层原理3.3 ArrayList vs LinkedList四、Vector和Stack了解即可4.1 Vector4.2 Stack五、泛型与集合5.1 为什么需要泛型5.2 泛型的使用5.3 泛型通配符六、Collections工具类七、面试高频考点考点1ArrayList的扩容机制考点2ArrayList vs LinkedList考点3遍历时删除元素八、总结参考资料一、集合框架概述1.1 为什么需要集合实际场景假设你要开发一个电商系统需要存储用户的购物车商品。用数组用户可能买1件也可能买100件数组长度固定怎么搞// 数组的缺点// 1. 长度固定不能动态扩展// 2. 只能存同一类型// 3. 提供的方法很少int[]arrnewint[10];// arr[10] 100; // 数组越界// 集合的优点// 1. 动态扩容大小可变// 2. 可以存不同类型通过泛型限制// 3. 提供丰富的方法增删改查、排序等经验之谈在实际项目中我几乎不用数组都是用ArrayList。只有在性能极其敏感的场景如大量原始数据计算才会考虑数组。1.2 集合框架体系Collection接口 ├── List接口 → 有序、可重复 │ ├── ArrayList → 动态数组 │ ├── LinkedList → 双向链表 │ ├── Vector → 动态数组线程安全已淘汰 │ └── Stack → 栈已淘汰用Deque代替 │ ├── Set接口 → 无序、不可重复 │ ├── HashSet → 哈希表 │ ├── LinkedHashSet → 哈希表链表保持插入顺序 │ └── TreeSet → 红黑树自动排序 │ └── Queue接口 → 队列 ├── LinkedList → 双向链表 ├── PriorityQueue → 优先队列堆 └── ArrayDeque → 双端队列 Map接口 → 键值对 ├── HashMap → 哈希表 ├── LinkedHashMap → 哈希表链表 ├── TreeMap → 红黑树 └── Hashtable → 线程安全已淘汰二、ArrayList最常用的集合2.1 ArrayList的基本使用importjava.util.ArrayList;importjava.util.List;// 创建ArrayListListStringlistnewArrayList();// 添加元素list.add(Java);list.add(Python);list.add(C);list.add(JavaScript);// 指定位置插入list.add(1,Go);// 在索引1的位置插入Go// 获取元素Stringslist.get(0);// Java// 修改元素list.set(0,JavaSE);// 把索引0的元素改为JavaSE// 删除元素list.remove(0);// 按索引删除list.remove(Python);// 按值删除只删第一个// 判断list.contains(C);// truelist.isEmpty();// falselist.size();// 4// 转数组String[]arrlist.toArray(newString[0]);// 清空list.clear();2.2 ArrayList的遍历ListStringlistnewArrayList();list.add(Java);list.add(Python);list.add(C);// 方式1普通for循环for(inti0;ilist.size();i){System.out.println(list.get(i));}// 方式2增强for循环推荐for(Strings:list){System.out.println(s);}// 方式3迭代器IteratorStringitlist.iterator();while(it.hasNext()){Stringsit.next();System.out.println(s);}// 方式4forEach LambdaJava 8list.forEach(s-System.out.println(s));// 方式5ListIterator可以反向遍历ListIteratorStringlitlist.listIterator();while(lit.hasNext()){System.out.println(lit.next());}2.3 ArrayList的底层原理面试重点踩坑提醒ArrayList的扩容是有性能代价的频繁扩容会导致大量数组拷贝。如果事先知道数据量一定要用new ArrayList(容量)指定初始容量。// ArrayList底层是一个Object数组transientObject[]elementData;// 默认初始容量privatestaticfinalintDEFAULT_CAPACITY10;// 扩容机制// 当添加元素时如果数组满了就扩容为原来的1.5倍// newCapacity oldCapacity (oldCapacity 1)// 10 → 15 → 22 → 33 → 49 → ...// 扩容过程// 1. 创建一个新数组容量为原来的1.5倍// 2. 把旧数组的元素复制到新数组// 3. 让elementData指向新数组经验之谈曾经优化过一个接口返回的数据量很大几万条但用了默认构造。通过指定初始容量接口响应时间从200ms降到了50ms。这个优化很简单但效果很明显。ArrayList扩容图解初始容量10 ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ J │ P │ C │ G │ │ │ │ │ │ │ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ 0 1 2 3 4 5 6 7 8 9 添加第11个元素时扩容为15 ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ J │ P │ C │ G │ R │ │ │ │ │ │ │ │ │ │ │ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 142.4 ArrayList的性能分析操作时间复杂度说明get(index)O(1)数组随机访问极快add(e)均摊O(1)末尾添加偶尔扩容add(index, e)O(n)中间插入需要移动元素remove(index)O(n)需要移动元素set(index, e)O(1)直接修改contains(e)O(n)遍历查找2.5 ArrayList的常见坑踩坑提醒ConcurrentModificationException是集合类最常见的异常之一很多新手都栽在这里。// 坑1遍历时删除元素ListStringlistnewArrayList(Arrays.asList(A,B,C,D));// ❌ 错误ConcurrentModificationExceptionfor(Strings:list){if(s.equals(B)){list.remove(s);// 报错}}// ✅ 正确方式1使用迭代器删除IteratorStringitlist.iterator();while(it.hasNext()){Stringsit.next();if(s.equals(B)){it.remove();// 用迭代器删除}}// ✅ 正确方式2倒序遍历删除for(intilist.size()-1;i0;i--){if(list.get(i).equals(B)){list.remove(i);}}// ✅ 正确方式3removeIfJava 8list.removeIf(s-s.equals(B));// 坑2Arrays.asList返回的List不能增删ListIntegerfixedListArrays.asList(1,2,3);// fixedList.add(4); // UnsupportedOperationException// Arrays.asList返回的是固定大小的List// ✅ 正确包装成ArrayListListIntegermutableListnewArrayList(Arrays.asList(1,2,3));mutableList.add(4);// OK原理揭秘增强for循环底层使用迭代器迭代器会检查集合是否被修改通过modCount计数器。如果用集合的remove方法修改了集合迭代器检测到modCount变化就会抛异常。三、LinkedList双向链表3.1 LinkedList的基本使用importjava.util.LinkedList;// 创建LinkedListLinkedListStringlistnewLinkedList();// List接口的方法和ArrayList一样list.add(Java);list.add(Python);list.add(C);list.get(0);list.set(0,JavaSE);list.remove(0);// LinkedList独有的方法双向链表操作list.addFirst(Go);// 在头部添加list.addLast(Rust);// 在尾部添加list.getFirst();// 获取头部元素list.getLast();// 获取尾部元素list.removeFirst();// 删除头部元素list.removeLast();// 删除尾部元素// 栈操作list.push(JavaScript);// 等价于addFirstlist.pop();// 等价于removeFirst// 队列操作list.offer(Kotlin);// 等价于addLastlist.poll();// 等价于removeFirstlist.peek();// 等价于getFirst3.2 LinkedList的底层原理// LinkedList底层是双向链表privatestaticclassNodeE{Eitem;// 数据NodeEnext;// 下一个节点NodeEprev;// 上一个节点}// 结构示意// null ← [Go] ←→ [Java] ←→ [Python] ←→ [C] ←→ [Rust] → null// ↑ ↑// first last3.3 ArrayList vs LinkedList面试高频考点这个问题几乎是Java面试的必考题一定要理解透彻。特性ArrayListLinkedList底层结构动态数组双向链表随机访问get(i)O(1)极快O(n)需要遍历头部插入/删除O(n)需要移动O(1)极快中间插入/删除O(n)O(n)查找O(n)操作O(1)尾部插入/删除O(1)均摊O(1)内存占用紧凑较大每个节点额外存两个指针CPU缓存友好连续内存不友好分散内存选择建议绝大多数场景用ArrayList随机访问多CPU缓存友好只在频繁头插头删时考虑LinkedList实际上LinkedList几乎不使用了用ArrayDeque代替栈/队列经验之谈很多人以为LinkedList插入删除快所以比ArrayList好。但实际上现代CPU的缓存机制让ArrayList在大多数场景下都更快。LinkedList的节点分散在堆内存中CPU缓存命中率低实际性能往往不如ArrayList。四、Vector和Stack了解即可4.1 Vector// Vector和ArrayList类似但所有方法都是synchronized线程安全VectorStringvectornewVector();vector.add(Java);vector.add(Python);// 已不推荐使用因为// 1. 每个方法都加锁性能差// 2. 如果需要线程安全用CopyOnWriteArrayList// 3. 如果不需要线程安全用ArrayList4.2 Stack// Stack继承Vector实现栈后进先出StackStringstacknewStack();stack.push(A);// 入栈stack.push(B);stack.push(C);Stringtopstack.peek();// 查看栈顶不删除Stringpopstack.pop();// 弹出栈顶// 已不推荐使用用Deque代替DequeStringdequenewArrayDeque();deque.push(A);deque.push(B);deque.pop();五、泛型与集合5.1 为什么需要泛型// 不用泛型可以存任何类型取出来需要强转ListlistnewArrayList();list.add(Hello);list.add(123);Strings(String)list.get(0);// 需要强转// String s2 (String) list.get(1); // ClassCastException// 用泛型类型安全不需要强转ListStringlist2newArrayList();list2.add(Hello);// list2.add(123); // 编译错误Strings3list2.get(0);// 不需要强转5.2 泛型的使用// 泛型类classPairK,V{privateKkey;privateVvalue;publicPair(Kkey,Vvalue){this.keykey;this.valuevalue;}publicKgetKey(){returnkey;}publicVgetValue(){returnvalue;}}// 使用PairString,IntegerpairnewPair(age,25);Stringkeypair.getKey();// 不需要强转Integervaluepair.getValue();// 泛型方法publicstaticTTgetFirst(ListTlist){if(listnull||list.isEmpty()){returnnull;}returnlist.get(0);}// 使用StringfirstgetFirst(Arrays.asList(A,B,C));5.3 泛型通配符踩坑提醒泛型通配符是Java泛型中最难理解的部分但掌握PECS原则就能迎刃而解。// ? extends Number上界通配符只读publicstaticdoublesum(List?extendsNumberlist){doubletotal0;for(Numbernum:list){totalnum.doubleValue();}returntotal;}// 可以传入ListInteger、ListDouble、ListLong等sum(Arrays.asList(1,2,3));// OKsum(Arrays.asList(1.0,2.0,3.0));// OK// ? super Integer下界通配符只写publicstaticvoidaddNumbers(List?superIntegerlist){list.add(1);list.add(2);}// 可以传入ListInteger、ListNumber、ListObjectaddNumbers(newArrayListInteger());// OKaddNumbers(newArrayListNumber());// OK// PECS原则Producer Extends, Consumer Super// 如果要从集合中读取生产者用? extends// 如果要往集合中写入消费者用? super记忆技巧extends → 上界 → 只能读取出来的至少是Numbersuper → 下界 → 只能写放进去的至少是Integer经验之谈Collections.copy方法就用了PECS原则——源列表用? extends T生产者只读目标列表用? super T消费者只写。六、Collections工具类importjava.util.Collections;ListIntegerlistnewArrayList(Arrays.asList(3,1,4,1,5,9));// 排序Collections.sort(list);// 升序Collections.sort(list,Collections.reverseOrder());// 降序// 查找intindexCollections.binarySearch(list,4);// 二分查找// 最大最小值Collections.max(list);Collections.min(list);// 填充Collections.fill(list,0);// 全部填充为0// 反转Collections.reverse(list);// 打乱Collections.shuffle(list);// 不可变集合ListStringimmutableCollections.unmodifiableList(list);// immutable.add(X); // UnsupportedOperationException// 空集合ListStringemptyCollections.emptyList();// 单元素集合ListStringsingleCollections.singletonList(Java);七、面试高频考点考点1ArrayList的扩容机制// 默认容量10// 扩容为原来的1.5倍newCapacity oldCapacity (oldCapacity 1)// 10 → 15 → 22 → 33 → 49 → 73 → ...延伸问题为什么扩容因子是1.5而不是2答案1.5是一个折中方案。2倍扩容可能导致内存浪费比如100万数据扩容到200万但只添加了1个元素1倍扩容即不扩容又会导致频繁扩容。1.5倍在内存使用和扩容频率之间取得了平衡。考点2ArrayList vs LinkedList// ArrayList基于数组随机访问O(1)中间插入删除O(n)// LinkedList基于链表随机访问O(n)头插头删O(1)// 实际开发中ArrayList性能更好CPU缓存友好追问什么情况下LinkedList比ArrayList快答案只有在频繁进行头部插入/删除操作时LinkedList才有优势。其他场景包括中间插入删除ArrayList往往更快因为CPU缓存局部性更好。考点3遍历时删除元素// 用迭代器的remove()方法// 或用removeIf()// 或倒序遍历删除原理增强for循环使用迭代器迭代器内部维护了一个modCount计数器。当检测到集合被非迭代器方式修改时就会抛出ConcurrentModificationException。八、总结今天我们学习了✅ 集合框架的体系结构✅ ArrayList的使用和底层原理✅ LinkedList的使用和底层原理✅ 泛型的使用和通配符✅ Collections工具类重点记忆ArrayList底层是数组随机访问快LinkedList底层是双向链表头插头删快实际开发中ArrayList更常用遍历时删除用迭代器或removeIfPECS原则Producer Extends, Consumer Super下一步预告Day13我们将学习集合框架下——Set、Map、HashMap原理。参考资料Oracle官方文档 - Collections FrameworkJava Generics FAQ - 泛型通配符详解互动话题你在使用ArrayList的时候有没有遇到过ConcurrentModificationException欢迎在评论区分享如果这篇文章对你有帮助欢迎点赞、收藏这是【JavaSE全面教学】系列的第12篇关注我看完整套教程本文为【JavaSE全面教学】系列第12篇持续更新中…

相关新闻