JAVA面试宝典-集合框架深度剖析: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异步编排实战
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值