十、java 集合框架

4 阅读6分钟

Java 集合框架


集合整体认知

什么是集合

  • 用来存储对象的容器(不能存基本类型,只能存包装类/对象)
  • 长度动态可变,对比数组更灵活
  • 提供丰富 API:增删改查、遍历、批量操作

集合与数组区别

  1. 数组固定长度,集合可变
  2. 数组可存基本类型/引用类型,集合只能存引用类型
  3. 数组功能简单,集合功能强大
  4. 数组效率略高,集合开发效率极高

集合体系两大分支

  • 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():获取迭代器

三种遍历方式

  1. 迭代器 Iterator(通用,遍历中可删除)
  2. 增强 for 循环(简洁,不能删除)
  3. 普通 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(必考)

特性ArrayListLinkedList
底层动态数组双向链表
查询快 O(1)慢 O(n)
增删慢 O(n)快 O(1)
内存占用少占用多(节点)
场景大量查询频繁增删

3. Set 接口(无序、不可重复、无索引)

特点

  • 不保证存取顺序
  • 不可重复
  • 无索引,不能用普通 for
  • 实现类:HashSet、LinkedHashSet、TreeSet

3.1 HashSet(最常用)

底层

基于 HashMap 实现,把元素当作 map 的 key

去重原理

  1. 先调用 hashCode() 获取哈希值
  2. 哈希值不同 → 直接存
  3. 哈希值相同 → 调用 equals()
  4. equals 相同 → 重复,不存
  5. 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 获取 value
  • remove(k):删除
  • containsKey(k) / containsValue(v)
  • keySet():获取所有 key
  • entrySet():获取键值对集合(推荐遍历方式)
  • values():获取所有 value

5. HashMap(企业使用最多)

底层结构(超级重点)

  • JDK1.7:数组 + 链表
  • JDK1.8+:数组 + 链表 + 红黑树

链表转红黑树规则

  1. 链表长度 ≥ 8
  2. 数组长度 ≥ 64 满足才转树,否则优先扩容

红黑树退化为链表

  • 节点数 ≤ 6 时退化

哈希冲突解决

  • JDK1.7:头插法
  • JDK1.8:尾插法(解决并发死链问题)

put 执行流程(面试必考)

  1. 第一次 put:初始化数组,容量 16
  2. 计算 key 的哈希值 hash = hash(key)
  3. 通过 (n-1) & hash 计算数组下标
  4. 位置为空 → 直接插入
  5. 位置不为空
    • key 相同 → 覆盖 value
    • 是树节点 → 树插入
    • 是链表 → 尾插,判断是否转树
  6. 扩容判断: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% 全覆盖)

  1. 集合体系结构?
  2. ArrayList 底层与扩容机制?
  3. HashMap JDK1.7 vs JDK1.8 区别?
  4. 链表转红黑树条件?
  5. HashSet 如何去重?
  6. HashMap 线程为什么不安全?
  7. ConcurrentHashMap 1.7 和 1.8 原理?
  8. ArrayList vs LinkedList 区别?
  9. HashMap 哈希冲突如何解决?
  10. 为什么重写 equals 必须重写 hashCode?
  11. Arrays.asList 坑点?
  12. 什么是 fail-fast 机制?
  13. CopyOnWrite 原理与适用场景?
  14. Map 有几种遍历方式?哪种最好?