Java 集合框架
集合整体认知
什么是集合
- 用来存储对象的容器(不能存基本类型,只能存包装类/对象)
- 长度动态可变,对比数组更灵活
- 提供丰富 API:增删改查、遍历、批量操作
集合与数组区别
- 数组固定长度,集合可变
- 数组可存基本类型/引用类型,集合只能存引用类型
- 数组功能简单,集合功能强大
- 数组效率略高,集合开发效率极高
集合体系两大分支
- Collection:单列集合
- List:有序、可重复、有索引
- Set:无序、不可重复、无索引
- Map:双列集合(key-value)
- key 不可重复,value 可重复
1. Collection 父接口(所有单列集合的父类)
通用方法(所有实现类都能用)
add(E e):添加元素addAll(Collection c):批量添加remove(Object o):删除指定元素clear():清空contains(Object o):是否包含isEmpty():是否为空size():元素个数toArray():转数组iterator():获取迭代器
三种遍历方式
- 迭代器 Iterator(通用,遍历中可删除)
- 增强 for 循环(简洁,不能删除)
- 普通 for(只有 List 支持)
2. List 接口(有序、可重复、有索引)
特点
- 存取有序
- 元素可重复
- 有索引,可以通过下标操作
- 实现类:ArrayList、LinkedList、Vector
2.1 ArrayList(最常用)
底层结构
- 数组实现:
transient Object[] elementData - 默认容量:10(JDK7 一开始就 10;JDK8 懒加载,第一次 add 才扩容为 10)
扩容机制
- 满了后:1.5 倍扩容
newCapacity = oldCapacity + oldCapacity >> 1- 底层调用
Arrays.copyOf复制数组
优缺点
- 优点:查询快(随机访问 O(1))、占用内存少
- 缺点:增删慢(需要移动元素 O(n))
线程安全
不安全,多线程下不能直接用
2.2 LinkedList
底层结构
- 双向链表
- 内部有 Node 节点:
item + next + prev
优缺点
- 优点:增删快(只改指针 O(1))
- 缺点:查询慢(要遍历 O(n))
特有方法
addFirst()、addLast()getFirst()、getLast()removeFirst()、removeLast()- 可做栈、队列、双端队列
2.3 Vector(古老,几乎不用)
- 底层也是数组
- 方法加
synchronized,线程安全 - 扩容:2 倍
- 效率低,被
CopyOnWriteArrayList替代
2.4 ArrayList vs LinkedList(必考)
| 特性 | ArrayList | LinkedList |
|---|---|---|
| 底层 | 动态数组 | 双向链表 |
| 查询 | 快 O(1) | 慢 O(n) |
| 增删 | 慢 O(n) | 快 O(1) |
| 内存 | 占用少 | 占用多(节点) |
| 场景 | 大量查询 | 频繁增删 |
3. Set 接口(无序、不可重复、无索引)
特点
- 不保证存取顺序
- 不可重复
- 无索引,不能用普通 for
- 实现类:HashSet、LinkedHashSet、TreeSet
3.1 HashSet(最常用)
底层
基于 HashMap 实现,把元素当作 map 的 key
去重原理
- 先调用
hashCode()获取哈希值 - 哈希值不同 → 直接存
- 哈希值相同 → 调用
equals() - equals 相同 → 重复,不存
- equals 不同 → 链表挂着
初始容量与加载因子
- 默认容量:16
- 加载因子:0.75
- 扩容:2 倍
线程不安全
3.2 LinkedHashSet
- 继承 HashSet
- 底层 LinkedHashMap
- 保证存取顺序
- 性能略低于 HashSet
3.3 TreeSet
- 底层 TreeMap(红黑树)
- 自动排序(自然排序 / 比较器排序)
- 元素必须可比较:
- 实现
Comparable接口 - 或传入
Comparator比较器
- 实现
- 无序但有序(排序有序)
- 不允许 null 元素
4. Map 接口(双列集合 key-value)
特点
- key:唯一、不可重复
- value:可重复
- key 允许一个 null(HashMap)
- 实现类:HashMap、LinkedHashMap、TreeMap、Hashtable、ConcurrentHashMap
通用方法
put(k,v):添加/修改get(k):根据 key 获取 valueremove(k):删除containsKey(k)/containsValue(v)keySet():获取所有 keyentrySet():获取键值对集合(推荐遍历方式)values():获取所有 value
5. HashMap(企业使用最多)
底层结构(超级重点)
- JDK1.7:数组 + 链表
- JDK1.8+:数组 + 链表 + 红黑树
链表转红黑树规则
- 链表长度 ≥ 8
- 数组长度 ≥ 64 满足才转树,否则优先扩容
红黑树退化为链表
- 节点数 ≤ 6 时退化
哈希冲突解决
- JDK1.7:头插法
- JDK1.8:尾插法(解决并发死链问题)
put 执行流程(面试必考)
- 第一次 put:初始化数组,容量 16
- 计算 key 的哈希值
hash = hash(key) - 通过
(n-1) & hash计算数组下标 - 位置为空 → 直接插入
- 位置不为空
- key 相同 → 覆盖 value
- 是树节点 → 树插入
- 是链表 → 尾插,判断是否转树
- 扩容判断:size > 阈值(容量*0.75)→ 2 倍扩容
线程安全
不安全,多线程会出现:
- 数据覆盖
- JDK1.7 可能死循环(链表成环)
- 数据丢失
6. LinkedHashMap
- 继承 HashMap
- 底层:哈希表 + 双向链表
- 保证遍历顺序 = 插入顺序
- 适合需要有序的缓存场景
7. TreeMap
- 底层:红黑树
- key 自动排序
- key 必须可比较
- 查询效率 O(logn)
- 不允许 null key
8. Hashtable(古老,不用)
- 线程安全(方法全加 synchronized)
- 效率极低
- 不允许 null key、null value
- 被 ConcurrentHashMap 替代
9. 线程安全集合(企业必知)
9.1 Vector
- 线程安全,低效,不推荐
9.2 Collections.synchronizedXXX
- 包装成线程安全集合
- 粒度粗,性能一般
9.3 CopyOnWriteArrayList
- 写时复制,读无锁
- 适合读多写少
9.4 CopyOnWriteArraySet
- 底层 CopyOnWriteArrayList
9.5 ConcurrentHashMap
- 线程安全,高性能
- JDK1.7:分段锁(Segment)
- JDK1.8:CAS + synchronized,红黑树
10. 集合使用场景总结(企业开发标准)
- 普通列表、查询多 → ArrayList
- 频繁增删、队列/栈 → LinkedList
- 去重、无序 → HashSet
- 去重、保序 → LinkedHashSet
- 排序 → TreeSet
- 普通键值对 → HashMap
- 键值对保序 → LinkedHashMap
- 键排序 → TreeMap
- 高并发线程安全 → ConcurrentHashMap、CopyOnWriteArrayList
11. 企业真实巨坑(高频 BUG 点)
坑1:ArrayList 并发修改异常 ConcurrentModificationException
遍历(增强 for/迭代器)时调用集合 add/remove 直接抛异常
解决:用迭代器删除,或用普通 for
坑2:HashMap 多线程死循环(JDK1.7)
并发扩容导致链表成环,CPU 100%
坑3:HashSet/HashMap 去重失效
对象只重写 equals 没重写 hashCode → 无法去重
坑4:基本类型数组转 List 陷阱
int[] arr = {1,2,3};
List list = Arrays.asList(arr); // 把整个数组当作一个元素
必须用 Integer[]
坑5:Arrays.asList 返回的 List 不可增删
List<String> list = Arrays.asList("a","b");
list.add("c"); // 抛 UnsupportedOperationException
解决:new ArrayList<>(Arrays.asList(...))
坑6:TreeSet/TreeMap 放不可比较对象
报 ClassCastException
坑7:foreach 遍历中删除元素
必抛并发修改异常
坑8:HashMap 大量哈希冲突导致性能雪崩
恶意构造相同 hashCode 数据,变成链表,性能从 O(1) → O(n)
坑9:集合存放 null 引发 NPE
如 TreeMap、Hashtable 不允许 null key
坑10:List.subList 强转 ArrayList 报错
subList 是内部类,不是 ArrayList,修改原集合会影响子列表
12. 高频面试题(100% 全覆盖)
- 集合体系结构?
- ArrayList 底层与扩容机制?
- HashMap JDK1.7 vs JDK1.8 区别?
- 链表转红黑树条件?
- HashSet 如何去重?
- HashMap 线程为什么不安全?
- ConcurrentHashMap 1.7 和 1.8 原理?
- ArrayList vs LinkedList 区别?
- HashMap 哈希冲突如何解决?
- 为什么重写 equals 必须重写 hashCode?
- Arrays.asList 坑点?
- 什么是 fail-fast 机制?
- CopyOnWrite 原理与适用场景?
- Map 有几种遍历方式?哪种最好?