集合框架核心知识

写 Java 代码离不开集合——ListMapSet 是每天都要打交道的工具。但很多人停留在"会用"层面,不知道:

  • ArrayList 的默认容量到底是多少?扩容倍数是多少?
  • HashMap 的容量为什么必须是 2 的幂次?
  • ConcurrentHashMap 怎么做到高效并发的?
  • HashMap 的树化阈值为什么是 8 而不是 16?

这些问题在面试中被追问到的频率极高。我自己也踩过坑——有一次线上服务 GC 频繁,最后排查发现是循环中往 HashMap 插入 40 万条数据,每次都触发扩容,重新 hash 了 18 次。

这套内容,就是帮你把这些集合"从源码层面彻底搞明白"。

内容总览

Java 集合框架是 JDK 中设计最精妙的模块之一。HashMap 的哈希算法、ConcurrentHashMap 的无锁设计、红黑树的阈值选择——每一处设计背后都有深意。

主题核心内容解决什么问题
ArrayList默认容量 0、首次扩容到 10、grow() 1.5 倍循环中不预设容量的性能陷阱
LinkedList双向链表、add/remove O(1)、无随机访问什么时候用 LinkedList 而不是 ArrayList
HashMap哈希算法、put 流程、树化阈值、扩容机制为什么 2 的幂次、为什么树化、红黑树阈值 8 是怎么来的
ConcurrentHashMapJDK7 分段锁 vs JDK8 CAS+ synchronized怎么做到高并发下不加锁读取
LinkedHashMap维护插入/访问顺序、accessOrder 实现 LRU用 LinkedHashMap 快速实现缓存淘汰
TreeMap红黑树实现、有序遍历、最近邻居查询需要按 key 排序时的选择
HashSet内部委托 HashMap、PRESENT 哨兵值为什么 HashSet 的元素必须重写 hashCode/equals

知识全景

ArrayList — 最常用的动态数组 🔴

ArrayList 源码与扩容机制

ArrayList 的默认容量不是 16,是 0。第一次 add 时才扩容到 10。这是 JDK 设计者做的一个优化——避免大多数场景下不必要的内存预分配。

扩容公式:newCapacity = oldCapacity + (oldCapacity >> 1),即 1.5 倍扩容。为什么不是 2 倍?空间和时间的一个平衡点。

⚠️

循环中往 ArrayList 添加大量元素时,一定要预设容量:new ArrayList<>(expectedSize)。否则每次 add 都可能触发 resize,时间复杂度从 O(n) 变成 O(n²)。

LinkedList — 双端队列 🟡

LinkedList 源码与双端队列

LinkedList 是双向链表,add/remove 在已知位置时是 O(1),但 get(i) 需要遍历是 O(n)。

很多人觉得"插入多就该用 LinkedList",但实际上大多数业务场景的瓶颈在于 get() 随机访问,而不是 add/remove。95% 的场景用 ArrayList 更好。

HashMap — 集合框架的核心 🔴

HashMap 是 Java 面试中权重最高的集合知识点,没有之一。

HashMap 源码深度解析 — put 流程、哈希算法、树化逻辑

HashMap 扩容机制 — JDK8 优化、e.hash & oldCap 原理

HashMap 红黑树化阈值设计 — 为什么是 8、泊松分布证明

HashMap 的核心设计要点:

  • 容量必须是 2 的幂次:index = (n-1) & hash,因为 (n-1) 二进制全是 1,& 代替 %
  • hash() 方法:(h = key.hashCode()) ^ (h >>> 16),把高位信息混入低位,减少碰撞
  • JDK8 引入红黑树:链表过长时退化为 O(n),树化为 O(log n)
  • 树化阈值 8:泊松分布计算表明,在负载因子 0.75 下链表长度达到 8 的概率极低
💡

JDK7 的扩容在并发场景下会导致死循环(两个线程同时 resize 时形成环形链表)。JDK8 用 e.hash & oldCap == 0 代替重新 hash,避免了这个问题。

ConcurrentHashMap — 高并发神器 🔴

ConcurrentHashMap JDK7 vs JDK8

ConcurrentHashMap put 流程

ConcurrentHashMap 扩容机制

ConcurrentHashMap 是并发编程中最常用的线程安全 Map:

  • JDK7:Segment 数组,每个 Segment 是一把 ReentrantLock,并发度 = 16
  • JDK8:去掉 Segment,改用 Node + CAS + synchronized,只锁单个桶
  • JDK8 不允许 null key/value(防止二义性)

LinkedHashMap — 有序 Map 🟡

LinkedHashMap 实现 LRU 缓存

LinkedHashMap 继承 HashMap,在每个节点上维护 before/after 指针,形成双向链表。设置 accessOrder=true 后,每次 access 都会移动到链表末尾——这就是 LRU 缓存的实现基础。

TreeMap — 有序 Map 🟡

TreeMap 与红黑树

TreeMap 用红黑树实现,key 必须可排序(自然顺序或 Comparator)。它的优势在于:O(log n) 的增删改查 + 有序遍历 + 范围查询(subMap)。

HashSet — Map 的委托 🟡

HashSet 与 HashMap 关系

HashSet 内部就是一个 HashMap,所有元素作为 key,value = 固定的 PRESENT 哨兵对象。HashMap 的 key 唯一性天然实现了 Set 的元素唯一性。

集合对比全景 🟡

ArrayList vs LinkedList 对比

HashMap vs Hashtable vs ConcurrentHashMap

选型建议:

  • 单线程 + 需要快速随机访问 → ArrayList
  • 单线程 + 需要有序 → TreeSet / TreeMap
  • 单线程 + 无特殊需求 → HashSet / HashMap
  • 并发 + 高性能 → ConcurrentHashMap
  • 并发 + 有序 → ConcurrentSkipListMap

面试关联

集合框架不是孤立的知识点:

阅读建议

|| 你的阶段 | 推荐顺序 | | --- | --- | | 面试冲刺 | HashMap → ConcurrentHashMap → ArrayList/LinkedList → 选型对比 | | 系统学习 | ArrayList → LinkedList → HashMap → HashMap 扩容 → HashMap 树化 → ConcurrentHashMap → LinkedHashMap → TreeMap | | 查漏补缺 | 直接找对应知识点定向阅读 |

记住:HashMap 是集合框架的灵魂。把它从 put 流程到扩容机制全部搞透,其他集合都是触类旁通。