这三大集合是 Java 面试和实际开发的必考点。下面我用面试视角 + 源码本质帮你快速理清它们的核心差异和底层原理。
一、ArrayList:动态数组的本质
核心:数组 + 扩容机制
1️⃣ 底层结构
transient Object\[\] elementData;
• 本质是 Object 数组
• size ≠ length(size 是实际元素个数)
2️⃣ 扩容机制(重点)
• 初始容量:
• new ArrayList<>() → 空数组(懒加载)
• 第一次 add 才扩容为 10
• 扩容公式:
int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5 倍
• 扩容代价高:要拷贝数组(System.arraycopy)
✅ 最佳实践
List list = new ArrayList<>(1000); // 预估容量,减少扩容
二、HashMap:Java 中最复杂的集合
核心:数组 + 链表 + 红黑树(JDK 8+)
1️⃣ 底层结构
Node<K,V>\[\] table
├── Node(链表)
└── TreeNode(红黑树)
2️⃣ 核心参数
参数 含义
默认容量 16
负载因子 0.75
树化阈值 链表长度 ≥ 8
树退化 ≤ 6
3️⃣ put 流程(高频面试题)
-
计算 hash:(h = key.hashCode()) ^ (h >>> 16)
-
判断 table 是否为空 → 初始化
-
计算下标:(n - 1) & hash
-
无冲突 → 直接插入
-
有冲突:
• 链表 → 追加
• 链表过长 → 树化
-
size > threshold → 扩容(2 倍)
4️⃣ 为什么线程不安全?
• 并发 put 可能:
• 数据覆盖
• 死循环(JDK 7 及以前)
• size 不准
✅ 线程安全替代
• Collections.synchronizedMap
• ConcurrentHashMap
三、ConcurrentHashMap:高并发下的王者
核心目标:高并发 + 高吞吐
1️⃣ JDK 7 vs JDK 8(重点)
版本 实现
JDK 7 Segment 分段锁(16 把锁)
JDK 8+ CAS + synchronized(锁粒度更细)
2️⃣ JDK 8 实现原理
Node<K,V>\[\] table
├── 链表
└── 红黑树
• 锁粒度:只锁当前桶(头节点)
• 读操作:完全无锁(volatile + Unsafe)
3️⃣ put 核心流程
- 定位桶
- 桶为空 → CAS 插入
- 桶不为空 → synchronized 锁头节点
- 链表 / 红黑树插入
- 判断是否树化
4️⃣ size() 怎么实现的?
• 不用全局锁
• 使用 CounterCell + baseCount
• 最终值 ≈ 近似值(高并发下允许误差)
四、三者对比总结(面试必背)
对比项 ArrayList HashMap ConcurrentHashMap
数据结构 数组 数组 + 链表 + 红黑树 数组 + 链表 + 红黑树
线程安全 ❌ ❌ ✅
扩容 1.5 倍 2 倍 2 倍
锁 无 无 CAS + synchronized
适用场景 顺序存储 KV 存储 高并发 KV
五、面试高频追问(你可以提前准备)
✅ HashMap 为什么长度是 2 的幂?
→ 位运算 (n - 1) & hash 分布更均匀
✅ 为什么负载因子是 0.75?
→ 时间与空间的平衡(泊松分布)
✅ ConcurrentHashMap 能完全替代 Hashtable 吗?
→ 几乎可以,但 size() 不是强一致
✅ 为什么不用 synchronized 锁整个 Map?
→ 并发度太低,性能差