Java Collection 集合进阶知识笔记

Collection 是 Java 集合框架中单列集合 的顶级接口,隶属于 java.util 包,与 Map 接口并列,定义了所有单列集合的通用行为,其核心子接口包括 List(有序可重复)、Set(无序不可重复)、Queue(队列)。进阶层面的 Collection 学习,需深入其设计思想、底层原理、性能优化及并发场景适配,而非仅停留在 API 使用层面。

一、Collection 体系核心设计思想

1. 接口分层与职责单一

Collection 框架采用 "接口 - 抽象类 - 实现类" 的分层设计,降低耦合性:

  • 顶级接口:Collection 定义通用方法(add、remove、contains、size 等),不关注具体实现;
  • 抽象类:AbstractCollection/AbstractList/AbstractSet 等封装通用逻辑(如迭代器、空值判断),实现类只需重写核心方法(如 get、add);
  • 实现类:ArrayList、LinkedList、HashSet 等专注于底层结构实现,复用抽象类的通用逻辑。

2. 迭代器模式(Iterator)

Collection 依赖 Iterator 接口实现统一遍历,核心优势是解耦遍历逻辑与集合底层结构

  • 迭代器的核心方法:hasNext()(判断是否有下一个元素)、next()(获取下一个元素)、remove()(删除当前元素,需在 next () 后调用);
  • 快速失败(fail-fast):迭代器遍历过程中,若集合被修改(如调用 add/remove),会抛出 ConcurrentModificationException(通过 modCount 与 expectedModCount 对比实现);
  • 安全失败(fail-safe):CopyOnWriteArrayList/CopyOnWriteArraySet 的迭代器基于集合副本遍历,修改操作不影响遍历,不会抛出异常(代价是内存占用翻倍)。

3. 泛型与类型安全

JDK 5 引入泛型后,Collection 支持类型限定,避免运行时类型转换异常:

  • 编译期检查:List<String> list = new ArrayList<>() 限定仅存储 String,编译时拒绝其他类型;
  • 泛型擦除:运行时泛型信息被擦除,底层仍为 Object 数组,需通过反射绕过泛型限制(不推荐);
  • 通配符:? extends T(上界通配符,只读)、? super T(下界通配符,只写),用于灵活适配多态场景。

二、核心子接口底层原理与进阶特性

1. List 接口(有序、可重复、索引访问)

(1)ArrayList(动态数组)
底层核心
  • 存储结构:基于 Object[] elementData 数组,默认初始容量 10(JDK 8 延迟初始化,首次 add 时才分配容量);
  • 扩容机制:扩容触发条件为 size == elementData.length,扩容后容量为原容量的 1.5 倍(newCapacity = oldCapacity + (oldCapacity >> 1)),扩容时需复制数组(Arrays.copyOf),性能损耗较大;
  • 缩容优化:JDK 1.8+ 提供 trimToSize() 方法,释放未使用的数组空间(将 elementData 容量缩为 size)。
进阶特性
  • 随机访问:通过索引访问元素时间复杂度 O (1),优于 LinkedList;
  • 插入 / 删除:尾部插入 O (1),中间插入 / 删除需移动元素(O (n)),批量插入可使用 addAll(int index, Collection<? extends E> c) 减少扩容次数;
  • 快速失败:迭代器遍历中修改集合会触发异常,若需边遍历边修改,可使用 ListIterator(支持正向 / 反向遍历,可添加 / 修改元素)。
(2)LinkedList(双向链表)
底层核心
  • 存储结构:基于双向链表,每个节点(Node)包含 prev(前驱)、next(后继)、item(元素),无数组扩容问题;
  • 索引访问:需从链表头 / 尾遍历到指定索引(O (n)),不支持随机访问;
  • 队列 / 栈适配:实现 Deque 接口,可作为栈(push/pop)、队列(offer/poll)、双端队列(offerFirst/offerLast)使用。
性能优化点
  • 批量操作:addAll(int index, Collection<? extends E> c) 只需修改链表指针,无需移动大量元素,性能优于 ArrayList;
  • 内存占用:每个节点额外存储前驱 / 后继指针,内存开销高于 ArrayList;
  • 遍历优化:使用迭代器遍历(Iterator)优于 for 循环(索引遍历),避免重复遍历链表。
(3)CopyOnWriteArrayList(并发安全)
核心原理
  • 写时复制:所有修改操作(add/remove/set)会复制一份新数组,修改完成后替换原数组,读操作直接访问原数组(无锁);
  • 线程安全:读操作无锁,写操作加锁(ReentrantLock)保证原子性,读写分离,适合 "读多写少" 场景;
  • 缺点:写操作内存开销大(复制数组),数据一致性为 "最终一致"(读操作可能读取到旧数据)。

2. Set 接口(无序、不可重复)

(1)HashSet
底层实现
  • 基于 HashMap 实现:底层维护一个 HashMap 实例,元素存储为 HashMap 的键,值为固定的 PRESENT(Object 常量);
  • 唯一性保证:依赖元素的 hashCode()equals() 方法,与 HashMap 键的判定规则一致;
  • 无序性:遍历顺序与插入顺序无关,由元素哈希值决定。
进阶注意点
  • 允许存储 null(仅一个),因 HashMap 允许键为 null;
  • 批量添加:addAll(Collection<? extends E> c) 底层调用 HashMap 的 putAll,性能优于逐个 add;
  • 去重逻辑:若添加已存在的元素,add() 方法返回 false,不会抛出异常。
(2)LinkedHashSet
底层实现
  • 继承自 HashSet,底层基于 LinkedHashMap 实现,维护双向链表保证插入顺序;
  • 有序性:遍历顺序与插入顺序一致,兼具 HashSet 的唯一性和 LinkedList 的有序性;
  • 性能:略低于 HashSet(需维护链表),但遍历性能更优。
(3)TreeSet
底层实现
  • 基于 TreeMap 实现,底层为红黑树结构,元素按自然顺序 / 自定义比较器排序;
  • 唯一性判定:通过比较器判断(compare(a,b) == 0 则视为重复),无需依赖 hashCode()equals()(但仍建议重写,保证与比较器逻辑一致);
  • 核心方法:ceiling(E e)(返回大于等于 e 的最小元素)、floor(E e)(返回小于等于 e 的最大元素)、subSet(E from, E to)(获取子集)。
(4)CopyOnWriteArraySet
底层实现
  • 基于 CopyOnWriteArrayList 实现,通过 addIfAbsent() 保证元素唯一性;
  • 适用场景:读多写少的并发场景,性能优于同步的 HashSet(Collections.synchronizedSet(new HashSet<>()))。

3. Queue 接口(队列,先进先出)

(1)ArrayDeque(数组双端队列)
底层核心
  • 存储结构:基于循环数组(Object [] elements),无容量限制(自动扩容),初始容量 16;
  • 双端操作:支持队首 / 队尾的插入(addFirst/addLast)、删除(removeFirst/removeLast),时间复杂度 O (1);
  • 对比 LinkedList:数组访问效率更高,内存开销更小,优先推荐使用 ArrayDeque 实现栈 / 队列。
(2)PriorityQueue(优先队列)
底层核心
  • 存储结构:基于二叉堆(完全二叉树),底层用数组实现;
  • 排序规则:默认按元素自然顺序升序排列,也可自定义 Comparator;
  • 核心操作:offer(E e)(插入元素,调整堆结构)、poll()(删除堆顶元素,调整堆结构),时间复杂度 O (logn);
  • 注意点:不允许存储 null,遍历结果无序(仅堆顶为最小 / 最大值)。
(3)BlockingQueue(阻塞队列,并发场景)
核心特性
  • 继承自 Queue,支持阻塞操作:
    • 入队:队列满时,put() 阻塞直到队列有空位;offer(E e, long timeout, TimeUnit unit) 超时阻塞;
    • 出队:队列空时,take() 阻塞直到队列有元素;poll(long timeout, TimeUnit unit) 超时阻塞;
  • 常见实现:
    • ArrayBlockingQueue:基于数组,有界队列,公平 / 非公平锁(默认非公平);
    • LinkedBlockingQueue:基于链表,默认无界(Integer.MAX_VALUE),可指定容量;
    • SynchronousQueue:无存储容量,入队必须等待出队,适用于线程间直接传递数据;
    • PriorityBlockingQueue:带优先级的阻塞队列,无界。

三、Collection 性能优化核心原则

1. 选择合适的实现类

场景 推荐实现类 避免使用 原因
随机访问、读多写少 ArrayList LinkedList ArrayList 索引访问 O (1),LinkedList O (n)
频繁插入 / 删除(中间位置) LinkedList ArrayList ArrayList 需移动元素,LinkedList 仅修改指针
并发读多写少 CopyOnWriteArrayList/CopyOnWriteArraySet Collections.synchronizedList() 无锁读,性能更优
并发队列操作 ArrayBlockingQueue/LinkedBlockingQueue 手动同步的 Queue 内置阻塞机制,无需额外加锁
去重 + 有序 LinkedHashSet HashSet + 手动排序 直接维护插入顺序,无需额外处理

2. 初始化容量优化

  • ArrayList/HashSet/HashMap:默认初始容量较小,若已知元素数量,初始化时指定容量(如 new ArrayList<>(1000)),避免多次扩容(扩容需复制数组,损耗性能);
  • 示例:new HashSet<>(200)(底层 HashMap 初始容量 200,减少扩容次数)。

3. 遍历方式性能对比

遍历方式 适用场景 性能排序(从优到差)
迭代器(Iterator) 所有 Collection 最优(直接操作底层结构,无额外计算)
增强 for 循环(for-each) 只读遍历 次优(底层编译为 Iterator)
普通 for 循环(索引) ArrayList(随机访问) 中等(仅适用于 List)
普通 for 循环(索引) LinkedList(链表) 最差(每次 get 遍历链表)

4. 批量操作优化

  • 优先使用 addAll()/removeAll()/retainAll() 等批量方法,减少循环调用单个方法的开销;
  • 示例:将 1000 个元素添加到 ArrayList,addAll() 只需 1 次扩容检查,逐个 add() 可能触发多次扩容。

四、Collection 并发安全问题与解决方案

1. 非线程安全的 Collection 类

ArrayList、LinkedList、HashSet、TreeSet、ArrayDeque 等,多线程下修改会导致:

  • 快速失败(ConcurrentModificationException);
  • 数据丢失、元素覆盖、迭代结果异常。

2. 并发安全解决方案

(1)同步包装器(Collections.synchronizedXxx ())
  • 用法:List<String> syncList = Collections.synchronizedList(new ArrayList<>())
  • 原理:所有方法加 synchronized 锁(锁对象为集合本身),保证原子性;
  • 缺点:锁粒度大,并发性能差(所有操作串行),遍历需手动加锁。
(2)并发集合(java.util.concurrent 包)
并发集合 适用场景 核心优势
CopyOnWriteArrayList 读多写少 读无锁,写复制数组,性能优于同步包装器
CopyOnWriteArraySet 读多写少、去重 基于 CopyOnWriteArrayList 实现
ConcurrentLinkedQueue 高并发、无界队列 无锁(CAS)实现,性能最优
ArrayBlockingQueue 有界阻塞队列 公平 / 非公平锁,适用于生产消费模型
LinkedBlockingQueue 无界 / 有界阻塞队列 分离锁(入队 / 出队不同锁),并发性能更高
(3)手动加锁

总结

Collection 作为 Java 单列集合的核心,其进阶学习的关键在于:

  • 使用 ReentrantLock 或 synchronized 手动控制集合的修改操作,适用于自定义并发逻辑;

  • 示例:

    java 复制代码
    Lock lock = new ReentrantLock();
    List<String> list = new ArrayList<>();
    
    public void add(String s) {
        lock.lock();
        try {
            list.add(s);
        } finally {
            lock.unlock();
        }
    }

    五、Collection 进阶面试考点

  • ArrayList 与 LinkedList 区别:底层结构、访问性能、插入删除性能、内存占用;

  • 快速失败与安全失败的区别:实现原理(modCount)、适用场景、异常类型;

  • CopyOnWriteArrayList 原理:写时复制、线程安全保证、适用场景;

  • 理解不同实现类的底层结构(数组、链表、红黑树、二叉堆),并根据场景选择合适的类;

  • 掌握并发场景下的安全方案(同步包装器、并发集合、手动加锁),平衡性能与安全性;

  • 优化集合操作(初始化容量、批量操作、遍历方式),减少不必要的性能损耗;

  • 区分快速失败与安全失败、写时复制等核心机制,避免并发问题。

    • PriorityQueue 底层实现:二叉堆、排序规则、核心操作复杂度;
    • HashSet 保证元素唯一的原理:基于 HashMap 键的唯一性,依赖 hashCode () 和 equals ();
    • BlockingQueue 核心实现:ArrayBlockingQueue(有界)、LinkedBlockingQueue(无界)、SynchronousQueue(无存储)的区别;
    • ArrayList 扩容机制:初始容量、扩容倍数、扩容流程(数组复制)。
相关推荐
大筒木老辈子8 小时前
C++笔记---并发支持库(atomic)
java·c++·笔记
BD_Marathon8 小时前
【JavaWeb】Servlet_url-pattern的一些特殊写法问题
java·开发语言·servlet
黄俊懿8 小时前
【深入理解SpringCloud微服务】Seata(AT模式)源码解析——开启全局事务
java·数据库·spring·spring cloud·微服务·架构·架构师
零度@8 小时前
Java中Map的多种用法
java·前端·python
中文很快乐8 小时前
java开发--开发工具全面介绍--新手养成记
java·开发语言·java开发·开发工具介绍·idea开发工具
IMPYLH8 小时前
Lua 的 Coroutine(协程)模块
开发语言·笔记·后端·中间件·游戏引擎·lua
550A8 小时前
如何修改kagglehub的数据集默认下载路径
python
倚天仗剑走天涯WGM8 小时前
对CANoe和VBA和TSmaster 三款工具的调用的理解
python
我命由我123458 小时前
python-dotenv - python-dotenv 快速上手
服务器·开发语言·数据库·后端·python·学习·学习方法