【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. 应用:合理选择集合类型、性能优化
相关推荐
code bean16 小时前
【LangChain】少样本提示(Few-Shot Prompting)实战指南
开发语言·python·langchain
青山师16 小时前
CompletableFuture深度解析:异步编程范式与源码实现
java·单例模式·面试·性能优化·并发编程
AI人工智能+电脑小能手16 小时前
【大白话说Java面试题 第42题】【JVM篇】第2题:JVM内存模型有哪些组成部分?
java·开发语言·jvm·面试
yqcoder16 小时前
深入理解 JavaScript:什么是可迭代对象 (Iterable)?
开发语言·javascript·网络
破阵子4432816 小时前
如何用 Claude Code 等 Agent 工具操作 MATLAB(支持代码编写及 Simulink)
开发语言·matlab
AI人工智能+电脑小能手16 小时前
【大白话说Java面试题 第43题】【JVM篇】第3题:GC分为哪两种?Young GC 和 Full GC有什么区别?
java·开发语言·jvm·后端·面试
Carino_U16 小时前
并发编程之CPU缓存架构&Disruptor
java·缓存·架构
Bear on Toilet17 小时前
【JSON-RPC远程过程调用组件库】测试报告
开发语言·软件测试·后端·自动化脚本
小雅痞17 小时前
[Java][Leetcode middle] 54. 螺旋矩阵
java·leetcode·矩阵
星恒随风17 小时前
C语言链表详解:从单链表到双向链表
c语言·开发语言·链表