说实话Java 集合这块我学了三遍。第一遍看视频感觉都懂了。第二遍看源码发现第一遍白看了。第三遍面试被问懵了又重新来。集合太重要了从 CRUD 到底层原理Java 后端面试必问。我的血泪教训是别死记硬背理解原理才能以不变应万变。先搞清楚整体架构Java 集合分成两大家族Collection和Map。Collection存单个对象Map存 key-value 对。Collection ├── List有序、可重复 │ ├── ArrayList │ ├── LinkedList │ └── Vector已废弃 │ ├── Set无序、不可重复 │ ├── HashSet │ ├── LinkedHashSet │ └── TreeSet │ └── Queue队列通常 FIFO ├── LinkedList也实现了 List ├── PriorityQueue └── Deque Map键值对 ├── HashMap ├── LinkedHashMap ├── TreeMap ├── Hashtable已废弃 └── ConcurrentHashMap面试官最喜欢从这个图开始问。List有序列表三剑客ArrayList vs LinkedList vs VectorArrayList底层是数组增删慢、查询快。get(int index)直接按下标访问O(1)。add(E e)往末尾加摊还分析后是 O(1)。但数组满了要扩容默认扩容 1.5 倍创建新数组把旧数据 copy 过去。扩容有成本面试会问扩容机制是什么答默认容量 10扩容时新容量 旧容量 * 1.5用Arrays.copyOf()复制数据。LinkedList底层是双向链表。没有扩容问题每个节点记录前后指针。add(E e)往末尾加O(1)。get(int index)要从头遍历O(n)。所以查多用 ArrayList增删多考虑 LinkedList。Vector古老的同步 List。所有方法都加了synchronized线程安全。但性能差JDK 1.0 的产物现在基本不用了。Stack 继承 Vector同理已废弃。实现类底层结构线程安全增删效率查询效率使用场景ArrayList数组不安全低扩容复制高 O(1)频繁查询LinkedList双向链表不安全高 O(1)低 O(n)频繁增删Vector数组安全低扩容复制高 O(1)基本不用我踩过的坑ArrayList 删除元素有坑。ListStringlistnewArrayList();list.add(a);list.add(b);list.add(c);for(Strings:list){if(b.equals(s)){list.remove(s);// ConcurrentModificationException}}for-each底层用的是 IteratorIterator 维护了一个expectedModCount如果 List 结构性修改add/remove次数不等于预期直接抛异常。正确写法是用 Iterator 的remove()IteratorStringitlist.iterator();while(it.hasNext()){Stringsit.next();if(b.equals(s)){it.remove();// 用 Iterator 的 remove}}Set无序不重复Set 的核心就一句话去重。但底层原理各不相同。HashSet哈希表实现HashSet底层是HashMap。对你没看错。HashSetE内部有一个HashMapE, Objectadd(E e)实际上是map.put(e, PRESENT)PRESENT是一个固定的空对象。所以 HashSet 的性能完全取决于 HashMap。不重复怎么做到的HashMap 的 key 不能重复HashSet 复用这个特性。LinkedHashSet保留插入顺序继承HashSet但多了一个双向链表维护顺序。原理内部有个LinkedHashMap每次 add 不仅算 hash还记录插入顺序。适合场景需要去重 保证遍历顺序。比如缓存可以用 LinkedHashSet。TreeSet有序 去重底层是TreeMap红黑树。add的时候用自然顺序或自定义比较器排序。所以放进去的元素必须实现Comparable或者构造时传Comparator。// 自然排序TreeSetIntegersetnewTreeSet();set.add(3);set.add(1);set.add(2);System.out.println(set);// [1, 2, 3]// 自定义排序TreeSetStringset2newTreeSet((a,b)-b.compareTo(a));set2.add(apple);set2.add(banana);System.out.println(set2);// [banana, apple]我踩过的坑TreeSet 放自定义对象要实现Comparable。classStudentimplementsComparableStudent{Stringname;intage;OverridepublicintcompareTo(Studento){// 按年龄排序returnthis.age-o.age;}}TreeSetStudentsetnewTreeSet();set.add(newStudent(张三,20));set.add(newStudent(李四,18));// 按年龄排序后[李四, 张三]没实现Comparable会抛ClassCastException。Map键值对三巨头HashMap最常用JDK 1.8 底层是数组 链表 红黑树。数组的每个位置叫桶buckethash 冲突的元素以链表形式挂在桶上链表长度 8 时转成红黑树查找从 O(n) 变 O(log n)红黑树节点数 6 时退化成链表put 流程1. 算 key 的 hash 值 2. 用 hash (n-1) 算数组下标 3. 桶为空直接放进去 4. 桶不为空遍历链表或红黑树 - key 存在更新 value - key 不存在插入 5. 检查是否需要扩容容量为什么是 2 的幂次因为hash (n-1)相当于取模运算2 的幂次时位运算最快。但 hash 值本身是散的如果 hash 函数不好下标会扎堆。JDK 1.8 用了hash 扰动函数把高 16 位和低 16 位异或让低位更散。我踩过的坑HashMap 作为 Key 的对象必须重写equals()和hashCode()。没重写的话默认是 Object 的 equals引用比较两个内容相同的对象会变成两个不同的 key。classPerson{Stringname;intage;// 重写这两个方法Overridepublicbooleanequals(Objecto){if(thiso)returntrue;if(onull||getClass()!o.getClass())returnfalse;Personperson(Person)o;returnageperson.ageObjects.equals(name,person.name);}OverridepublicinthashCode(){returnObjects.hash(name,age);}}LinkedHashMapHashMap 顺序和 LinkedHashSet 一样额外维护一个双向链表。支持两种顺序插入顺序默认按 put 的先后遍历访问顺序LRU 缓存用它// 访问顺序最近访问的放最后MapString,StringcachenewLinkedHashMap(16,0.75f,true);可以用它实现 LRU 缓存removeEldestEntry()返回 true 时自动删最老的。TreeMap红黑树实现和 TreeSet 同理底层是红黑树key 必须可比较。支持自定义比较器按 key 排序。HashMap vs Hashtable vs ConcurrentHashMap实现类线程安全底层结构null 支持性能HashMap不安全数组链表红黑树key/value 都可以为 null高Hashtable安全全局锁数组链表都不行低ConcurrentHashMap安全分段锁/CAS数组链表红黑树key/value 都不能为 null中Hashtable 基本废弃了面试会问ConcurrentHashMap 怎么保证线程安全答JDK 1.7 用 Segment 分段锁1.8 改成了 CAS synchronized直接锁住数组元素。Queue队列接口常见实现LinkedList可以当队列用FIFO。QueueStringqueuenewLinkedList();queue.offer(first);queue.offer(second);Stringsqueue.poll();// firstPriorityQueue优先级队列底层是二叉堆。元素按自然顺序或自定义顺序排列poll()取出的永远是最小或最大的。PriorityQueueIntegerpqnewPriorityQueue();pq.offer(3);pq.offer(1);pq.offer(2);pq.poll();// 1Deque双端队列两头都能进出。ArrayDeque 比 LinkedList 快数组实现生产环境优先用它。阻塞队列并发编程里经常用ArrayBlockingQueue、LinkedBlockingQueue。put()队列满则阻塞take()队列空则阻塞选型指南场景推荐选择原因需要快速查询ArrayList HashMap数组下标访问 O(1)需要频繁增删头尾LinkedList ArrayDeque链表/数组头尾操作 O(1)需要去重HashSethash 去重O(1)需要有序去重TreeSet红黑树排序需要保序去重LinkedHashSet双向链表维护顺序并发安全ConcurrentHashMapCAS synchronized需要 LRU 缓存LinkedHashMap访问顺序模式面试高频问题Q1ArrayList 扩容源码流程add()时检查 sizesize 数组长度调用grow()扩容新容量 旧容量 (旧容量 1)即 1.5 倍创建新数组用Arrays.copyOf()复制数据Q2HashMap 什么时候转红黑树链表长度 8 且数组长度 64。如果数组长度 64先扩容解决 hash 冲突不转树。Q3HashMap 线程安全吗怎么在多线程环境用不安全。并发 put 可能导致死循环JDK 1.7 扩容时。多线程环境用Collections.synchronizedMap(new HashMap())全局锁不推荐ConcurrentHashMap推荐Q4HashSet 和 HashMap 的区别HashSet 底层就是 HashMapSet 是 Map 的 key 集合。HashSet存单个值不重复HashMap存键值对通过 key 去重记忆口诀List 查快改慢用数组Linked 改快查慢用链表Set 去重靠 HashTree 排序靠二叉Map 选型看场景Concurrent 安全高并发总结Java 集合这块核心就三条线第一条数据结构。数组、链表、树、哈希表搞清楚每个实现的底层用的是什么。第二条时间复杂度。add/remove/get 在各实现里的复杂度面试必问。第三条线程安全。哪些是安全的哪些不安全并发场景用什么。把这三条串起来集合就差不多了。