【java-core-collections】集合框架深度解析

集合框架深度解析

一、List 集合

1.1 ArrayList

底层数据结构: Object[] 数组

java 复制代码
// 扩容机制
private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    // 新容量 = 旧容量 * 1.5
    int newCapacity = ArraysSupport.newLength(oldCapacity,
        minCapacity - oldCapacity, oldCapacity >> 1);
    return Arrays.copyOf(elementData, newCapacity);
}

扩容流程:

复制代码
初始: 空数组 {} (懒初始化)
首次 add: 默认容量 10
第 11 次 add: 10 → 15 (×1.5)
第 16 次 add: 15 → 22 (×1.5)

时间复杂度:

操作 复杂度 原因
get(index) O(1) 数组随机访问
add(e) 尾部 O(1) 均摊 偶尔扩容 O(n)
add(index, e) O(n) 需要移动元素
remove(index) O(n) 需要移动元素

1.2 ArrayList vs LinkedList

特性 ArrayList LinkedList
底层结构 数组 双向链表
随机访问 O(1) O(n)
头部插入 O(n) O(1)
内存占用 紧凑 每节点额外2个指针
缓存友好性 好(连续内存) 差(分散内存)

结论: 99% 场景用 ArrayList,除非频繁头部插入。

1.3 ArrayList 线程安全问题

java 复制代码
// ConcurrentModificationException
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String s : list) {
    if ("b".equals(s)) list.remove(s); // 抛异常!
}

// 正确方式1: Iterator.remove()
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if ("b".equals(it.next())) it.remove();
}

// 正确方式2: CopyOnWriteArrayList
List<String> safeList = new CopyOnWriteArrayList<>(list);

二、Map 集合

2.1 HashMap 原理

数据结构:数组 + 链表 + 红黑树(JDK 8+)

复制代码
table[]
┌────┐
│ 0  │ → null
├────┤
│ 1  │ → Node(K1,V1) → Node(K2,V2) → Node(K3,V3)
├────┤
│ 2  │ → TreeNode (红黑树)
├────┤
│ 3  │ → null
├────┤
│... │
└────┘

put 流程:

复制代码
1. 计算 hash = (h = key.hashCode()) ^ (h >>> 16)  // 扰动函数
2. 计算索引 = (n - 1) & hash                        // 等价 hash % n
3. 桶为空 → 直接放入
4. 桶非空:
   - key 相同 → 覆盖 value
   - 是 TreeNode → 红黑树插入
   - 是链表 → 尾插法,长度 ≥ 8 时转红黑树
5. 检查容量 > threshold → 扩容

2.2 HashMap 扩容机制

java 复制代码
// 核心参数
static final int DEFAULT_INITIAL_CAPACITY = 16;    // 默认容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;    // 负载因子
static final int TREEIFY_THRESHOLD = 8;            // 链表→红黑树
static final int UNTREEIFY_THRESHOLD = 6;           // 红黑树→链表
static final int MIN_TREEIFY_CAPACITY = 64;         // 树化最小容量

// 扩容条件
size > capacity * loadFactor  →  capacity × 2

为什么容量是 2 的幂?

  • (n - 1) & hash 等价于 hash % n,位运算更快
  • 扩容时,元素要么在原位置,要么在原位置+旧容量,无需重新计算 hash

2.3 JDK 7 vs JDK 8 HashMap

特性 JDK 7 JDK 8
链表插入 头插法(多线程成环) 尾插法
链表过长 始终链表 转 TreeNode(红黑树)
hash 计算 9 次扰动 1 次扰动
扩容 重新计算所有位置 原位置 or 原位置+旧容量

2.4 HashMap 为何线程不安全

JDK 7: 并发扩容时头插法导致链表成环 → 死循环

JDK 8: 并发 put 时数据覆盖

java 复制代码
// 两个线程同时判断桶为空,都执行写入
if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);
// 后一个覆盖前一个

2.5 ConcurrentHashMap(JDK 8)

java 复制代码
// 初始化(CAS)
if (tab == null || tab.length == 0)
    tab = initTable();  // CAS 保证只有一个线程初始化

// put 操作
Node<K,V> f; int n, i;
if (tab == null || (n = tab.length) == 0)
    tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
    // 桶为空:CAS 写入
    if (casTabAt(tab, i, null, new Node<>(hash, key, value)))
        break;
} else {
    // 桶非空:synchronized 头节点
    synchronized (f) {
        // 遍历链表/红黑树插入
    }
}

三、Set 集合

3.1 HashSet

java 复制代码
// HashSet 底层就是 HashMap
public class HashSet<E> {
    private transient HashMap<E,Object> map;
    private static final Object PRESENT = new Object();

    public boolean add(E e) {
        return map.put(e, PRESENT) == null;
    }
}

3.2 TreeSet

  • 底层 TreeMap(红黑树)
  • 元素有序(自然排序或 Comparator)
  • 添加/删除/查询 O(log n)

四、Queue 集合

4.1 PriorityQueue

java 复制代码
// 底层是最小堆(数组实现)
// offer: 上浮(sift up)  O(log n)
// poll:  下沉(sift down) O(log n)
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(3); pq.offer(1); pq.offer(2);
pq.poll(); // 1(最小元素)

4.2 阻塞队列

队列 特点 适用场景
ArrayBlockingQueue 有界数组 线程池生产者消费者
LinkedBlockingQueue 可选有界链表 Executors 默认
SynchronousQueue 不存储元素 直接传递任务
PriorityBlockingQueue 无界优先级 优先级任务
DelayQueue 延迟取出 定时任务
java 复制代码
// 生产者-消费者模式
BlockingQueue<String> queue = new ArrayBlockingQueue<>(100);

// 生产者
queue.put("任务");  // 满时阻塞

// 消费者
String task = queue.take();  // 空时阻塞

五、面试高频题

Q1: HashMap 的底层数据结构?

考点 :HashMap 原理
难度 :⭐⭐⭐
答案:JDK 8 是数组 + 链表 + 红黑树。数组默认容量 16,负载因子 0.75。hash 冲突时形成链表,链表长度 ≥ 8 且数组长度 ≥ 64 转为红黑树,红黑树节点 ≤ 6 退回链表。

Q2: HashMap 的 hash 方法为什么用扰动函数?

考点 :hash 优化
难度 :⭐⭐⭐
答案(h ^ (h >>> 16)) 让高 16 位也参与索引计算,减少碰撞。因为容量通常较小,只有低位参与索引,高位不同但低位相同的 key 会冲突。

Q3: HashMap 和 ConcurrentHashMap 的区别?

考点 :并发容器
难度 :⭐⭐⭐
答案:HashMap 线程不安全;ConcurrentHashMap 线程安全。JDK 8 CHM 使用 CAS + synchronized(锁头节点),粒度细到桶级别,并发度等于数组长度。

Q4: ArrayList 的扩容机制?

考点 :动态数组
难度 :⭐⭐
答案:默认初始容量 10(首次 add 时),每次扩容为原来的 1.5 倍。使用 Arrays.copyOf 复制旧数组到新数组。

Q5: HashMap 为什么用红黑树而不是 AVL 树?

考点 :数据结构选择
难度 :⭐⭐⭐⭐
答案:红黑树插入/删除时旋转次数更少(最多 3 次 vs AVL 的 O(log n) 次),综合增删查性能更好。HashMap 场景增删频繁,红黑树更合适。

Q6: fail-fast 和 fail-safe 的区别?

考点 :迭代器机制
难度 :⭐⭐⭐
答案:fail-fast(ArrayList/HashMap):遍历时修改抛 ConcurrentModificationException,通过 modCount 检测。fail-safe(CopyOnWriteArrayList/ConcurrentHashMap):遍历副本或弱一致性,允许并发修改。

Q7: Comparable 和 Comparator 的区别?

考点 :排序接口
难度 :⭐⭐
答案:Comparable 在类内部实现 compareTo(),定义自然排序。Comparator 是外部比较器,实现 compare(),可以定义多种排序策略。Comparator 更灵活,符合开闭原则。

Q8: CopyOnWriteArrayList 的原理和适用场景?

考点 :并发容器
难度 :⭐⭐⭐
答案:写入时复制整个数组。读无锁,写加锁并复制。适合读多写极少(事件监听器列表、配置列表)。不适用写频繁场景(每次 O(n) 复制)。迭代器遍历的是快照。

Q9: HashMap 的负载因子为什么是 0.75?

考点 :HashMap 参数
难度 :⭐⭐⭐⭐
答案:时间和空间的折中。0.5 太浪费空间;1.0 碰撞太多。0.75 时泊松分布计算,每个桶 8 个以上元素的概率 < 千万分之一。也是 0.75 浮点数在二进制中精确表示。

Q10: ConcurrentHashMap 能完全替代 Hashtable 吗?

考点 :并发容器
难度 :⭐⭐⭐
答案:基本可以。ConcurrentHashMap 性能远优于 Hashtable(后者每个方法都 synchronized)。但 Hashtable 支持 null key/value,CHM 不支持。Hashtable 已被标记过时。

学习路径

  1. 基础:ArrayList、LinkedList、HashMap 使用
  2. 原理:HashMap 扩容、红黑树转换、hash 计算
  3. 并发:ConcurrentHashMap 实现、CopyOnWriteArrayList
  4. 应用:合理选择集合类型、性能优化
相关推荐
geovindu1 小时前
go: Bridge Pattern
开发语言·设计模式·golang·软件构建·桥接模式
小江的记录本1 小时前
【分布式】分布式系统核心知识体系:CAP定理、BASE理论与核心挑战
java·前端·网络·分布式·后端·python·安全
Fate_I_C1 小时前
Kotlin 为什么是 Android 开发的首选语言
android·开发语言·kotlin
ch.ju2 小时前
Java程序设计(第3版)第二章——switch case break
java
曹牧2 小时前
Spring MVC中使用HttpServletRequest和HttpServletResponse
java·spring·mvc
格鸰爱童话2 小时前
python录音转文字
开发语言·python
常利兵2 小时前
Kotlin 助力 Android 启动“大提速”
android·开发语言·kotlin
黎梨梨梨_2 小时前
C++入门基础(上)(namespace和缺省参数)
开发语言·c++
我命由我123452 小时前
Android 开发,getSystemService 警告信息:Must be one of: Context. POWER_SERVICE ...
android·java·java-ee·android studio·android jetpack·android-studio·android runtime