集合框架深度剖析:ArrayList底层扩容策略
一、ArrayList核心机制
1.1 底层数组结构
// JDK 1.8源码
transient Object[] elementData; // 实际存储容器
private int size; // 当前元素数量
1.2 扩容算法详解
扩容触发条件:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 关键扩容方法
elementData[size++] = e;
return true;
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍扩容
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
elementData = Arrays.copyOf(elementData, newCapacity); // 数据拷贝
}
扩容轨迹示例:
初始容量10 → 添加第11元素 → 扩容至15 → 添加第16元素 → 扩容至22 → ...
1.3 初始化策略对比
// 三种构造方法对比
List<String> list1 = new ArrayList<>(); // 默认初始容量10
List<String> list2 = new ArrayList<>(100); // 指定初始容量
List<String> list3 = new ArrayList<>(list); // 使用已有集合初始化
二、HashMap红黑树转换机制
2.1 数据结构演进
数组 + 链表 → 数组 + 链表/红黑树(JDK8+)
2.2 树化触发条件
static final int TREEIFY_THRESHOLD = 8; // 链表长度阈值
static final int MIN_TREEIFY_CAPACITY = 64; // 最小数组容量
// 树化逻辑(HashMap源码)
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
2.3 退化链表场景
当红黑树节点数 ≤ UNTREEIFY_THRESHOLD(6) 时
调整大小导致树节点数 < 阈值
三、ConcurrentHashMap线程安全实现
3.1 锁粒度演进
JDK7:Segment分段锁(16段)
JDK8+:Node+CAS+synchronized
3.2 put操作源码解析
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
break; // CAS插入空桶
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f); // 协助扩容
else {
// synchronized锁住链表头节点
synchronized (f) {
// 链表/红黑树插入逻辑
}
}
}
addCount(1L, binCount);
return null;
}
四、集合使用陷阱与优化
4.1 遍历删除的正确姿势
错误方式:
for (int i=0; i<list.size(); i++) {
if (condition) {
list.remove(i); // 导致索引错位
}
}
for (String s : list) {
if (condition) {
list.remove(s); // 触发ConcurrentModificationException
}
}
正确方式:
// 迭代器删除
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (condition) {
it.remove(); // 安全删除
}
}
// 倒序遍历删除
for (int i=list.size()-1; i>=0; i--) {
if (condition) {
list.remove(i);
}
}
4.2 性能优化技巧
预分配容量:
// ArrayList优化
List<Integer> list = new ArrayList<>(10000);
list.ensureCapacity(10000); // 提前扩容
// HashMap优化
Map<String, Object> map = new HashMap<>(1024, 0.75f);
批量操作:
// 使用addAll替代循环添加
list.addAll(anotherList);
// 使用subList视图(注意共享修改)
List<String> sub = list.subList(0, 5);
五、高频面试题解析
1.ArrayList与LinkedList区别?
- 底层结构:数组 vs 双向链表
- 时间复杂度:
- 随机访问:O(1) vs O(n)
- 头尾插入:ArrayList需要扩容 vs O(1)
- 中间插入:O(n) vs O(n)(但LinkedList不需要移动数据)
2.HashMap为什么用红黑树不用AVL树?
- 红黑树插入/删除效率更高(旋转次数更少)
- 红黑树对磁盘存储更友好(B树基础)
- 查询时间复杂度O(logn)可接受
3. ConcurrentHashMap的size方法原理?
- JDK7:分段计算后求和(可能不准确)
- JDK8+:通过baseCount和CounterCell数组统计
- 最终调用sumCount()获取近似值
4. 快速失败(fail-fast)机制如何实现?
- 通过modCount字段记录修改次数
- 迭代时检查modCount != expectedModCount
- 检测到并发修改抛出ConcurrentModificationException
实战避坑指南
1.慎用Arrays.asList()
List<Integer> list = Arrays.asList(1,2,3);
list.add(4); // 抛出UnsupportedOperationException
// 原因:返回的是固定大小的Arrays$ArrayList
2.避免在foreach里修改集合
List<String> list = new ArrayList<>(Arrays.asList("a","b"));
for (String s : list) {
if ("a".equals(s)) {
list.remove(s); // 抛出ConcurrentModificationException
}
}
3.注意包装类集合的contains
List<Integer> list = new ArrayList<>();
list.add(1);
System.out.println(list.contains("1")); // false
// 类型不匹配导致查找失败
下篇预告:《并发编程必考:synchronized与AQS原理》将揭秘:
- 对象内存布局与锁升级过程
- ReentrantLock公平锁实现
- volatile内存屏障原理
- ThreadLocal内存泄漏解决方案
- CompletableFuture异步编排实战