网站首页 > 精选教程 正文
《Java List 体系中 ArrayList、LinkedList 和 CopyOnWriteArrayList 的原理解析与场景适配》
在 Java List 体系中,除 Vector 外,ArrayList、LinkedList、CopyOnWriteArrayList 是最核心的实现类。以下从底层数据结构、关键方法实现、设计权衡三个维度,深度解析它们的原理:
一、ArrayList:动态数组的高效随机访问
核心数据结构
// JDK 17 源码(简化) public class ArrayList<E> extends AbstractList<E> implements List<E> { private static final int DEFAULT_CAPACITY = 10; // 默认容量 transient Object[] elementData; // 存储元素的数组 private int size; // 实际元素数量 } |
关键方法原理
- add(E e)(末尾添加)
- 检查容量:若 size == elementData.length,触发扩容(grow() 方法)。扩容逻辑:新容量 = oldCapacity * 1.5(位运算优化:oldCapacity + (oldCapacity >> 1))。赋值:elementData[size++] = e(O (1) 摊还时间,扩容时复制数组为 O (n))。
- get(int index)(随机访问)
- 直接返回 (E) elementData[index](O (1) 时间,数组下标直接寻址)。边界检查:index < 0 || index >= size 抛 IndexOutOfBoundsException。
- remove(int index)(中间删除)
- 移动元素:System.arraycopy(elementData, index + 1, elementData, index, size - index - 1)(O (n) 时间,后续元素前移)。缩容优化:若删除后容量利用率 <1/3 且容量> DEFAULT_CAPACITY,触发 trimToSize()(非自动,需手动调用)。
设计权衡
- 空间换时间:预留冗余空间减少扩容频率,但可能浪费内存(如初始 10 容量仅存 1 元素)。
- 快速失败(Fail-Fast):迭代器依赖 modCount 检测并发修改,保证遍历安全。
二、LinkedList:双向链表的灵活增删
核心数据结构
// JDK 17 源码(简化) public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E> { private static class Node<E> { E item; Node<E> prev; // 前驱节点 Node<E> next; // 后继节点 } transient int size; transient Node<E> first; // 头节点 transient Node<E> last; // 尾节点 } |
关键方法原理
- addFirst(E e)(头插)
- 创建新节点:new Node<>(null, e, first)。头节点更新:若原头节点非空,first.prev = newNode,否则 last = newNode。O (1) 时间,仅修改指针无需移动元素。
- get(int index)(随机访问)
- 二分查找:若 index < size / 2 从头遍历,否则从尾遍历(node(index) 方法)。遍历逻辑:Node<E> x = first; for (int i = 0; i < index; i++) x = x.next;(O (n/2) 时间,平均 O (n))。
- remove(Node<E> node)(删除节点)
- 前驱后继重连:node.prev.next = node.next; node.next.prev = node.prev。头尾指针更新:若删除头节点,first = node.next;若删除尾节点,last = node.prev。O (1) 时间,仅修改指针(定位节点需 O (n))。
设计权衡
- 时间换空间:每个节点存储前驱 / 后继引用(额外 2 个指针),适合频繁增删但内存充足的场景。
- 无连续内存:链表节点分散存储,CPU 缓存命中率低,遍历性能低于数组。
三、CopyOnWriteArrayList:写时复制的高并发遍历
核心数据结构
// JDK 17 源码(简化) public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess { private transient volatile Object[] array; // 存储元素的数组(volatile 保证可见性) final Object[] getArray() { return array; } final void setArray(Object[] a) { array = a; } } |
关键方法原理
- add(E e)(写操作)
- 加锁:ReentrantLock 保证原子性(lock.lock())。复制数组:Object[] newArray = Arrays.copyOf(array, array.length + 1)。赋值:newArray[newArray.length - 1] = e,更新 array = newArray(O (n) 时间,复制数组)。解锁:lock.unlock()。
- iterator()(迭代器)
- 快照机制:迭代器持有当前数组的引用(snapshot = getArray()),遍历时不感知后续修改。遍历时无需加锁:读操作无锁,允许脏读(适合读多写少场景)。
- set(int index, E e)(修改元素)
- 加锁后复制数组,修改指定位置元素(与 add 类似,需复制整个数组)。O (n) 时间,不适合频繁修改(如批量更新)。
设计权衡
- 最终一致性:迭代器遍历的是写操作前的快照,新元素对正在进行的遍历不可见。
- 内存与并发的权衡:写时复制牺牲内存(双倍数组)换取无锁读,适合事件监听、配置缓存等高并发读场景。
四、原理对比:数据结构决定性能
操作类型 | ArrayList(动态数组) | LinkedList(双向链表) | CopyOnWriteArrayList(写时复制) |
随机访问 | 数组下标直接访问(O (1)) | 遍历链表(O (n)) | 数组下标直接访问(O (1)) |
末尾添加 | 直接赋值(摊还 O (1)) | 尾节点指针修改(O (1)) | 复制数组(O (n)) |
中间插入 | 移动元素(O (n)) | 修改指针(定位 O (n),操作 O (1)) | 复制数组(O (n)) |
迭代安全性 | Fail-Fast(抛异常) | Fail-Fast(抛异常) | 弱一致性(不抛异常) |
内存布局 | 连续内存(高缓存命中率) | 离散内存(低缓存命中率) | 双倍内存(写时复制) |
五、典型场景的原理适配
- 报表数据展示(10 万条,读多写少)→ ArrayList,预分配容量减少扩容:new ArrayList<>(100000),get(index) 直接数组寻址,避免链表遍历。
- 消息队列(高频头尾操作)→ LinkedList 或 ArrayDeque,offerLast()/pollFirst() 仅改指针:LinkedList 实现 Deque,头尾操作均为 O (1)。
- 微服务配置中心(全局只读,偶尔更新)→ CopyOnWriteArrayList,无锁遍历配置项:管理员更新配置时复制数组,不影响正在遍历的客户端(最终一致性)。
- 链表算法开发(如反转链表)→ LinkedList,直接操作节点指针:无需像数组一样移动元素,prev/next 指针操作更直观。
六、避坑:原理中的陷阱
- ArrayList 扩容的性能陷阱
- 反例:循环中 add 小容量(如 new ArrayList() 初始 10,第 11 次扩容为 15,第 16 次 22...)。正例:预估数据量,new ArrayList<>(expectedSize) 避免多次扩容。
- LinkedList 的随机访问陷阱
- 反例:for (int i = 0; i < list.size(); i++) list.get(i),每次 get 遍历链表(O (n^2) 时间)。正例:改用迭代器 Iterator 或 ListIterator 遍历(O (n) 时间)。
- CopyOnWriteArrayList 的内存爆炸
- 反例:大集合高频写(如每秒 1000 次 add),每次复制数组导致内存溢出。正例:仅用于读多写少且集合较小的场景(如事件监听列表,通常不超过 100 个监听者)。
总结:原理决定选择
- 数组优势:随机访问快,内存紧凑(ArrayList)。
- 链表优势:头尾操作快,无扩容开销(LinkedList)。
- 写时复制优势:无锁读,弱一致性(CopyOnWriteArrayList)。
理解这些实现类的底层原理,能精准匹配业务场景,避免 “用链表实现随机列表”“用写时复制处理高频写” 等设计错误,写出高性能、易维护的代码。
- 上一篇: 如何用Python快速反转链表
- 下一篇: 图解:K 个一组翻转链表 | 眼睛会了手就会系列
猜你喜欢
- 2025-05-14 全网讲解最透彻:HashMap&ConcurrentHashMap总结 等你来看
- 2025-05-14 Python | Leetcode链表系列(上篇)
- 2025-05-14 为了面试字节跳动后端开发岗(Java)鬼知道我经历了什么..
- 2025-05-14 java面试必备:七个常见的Java算法问题和示例答案
- 2025-05-14 1年半经验,2本学历,Curd背景,竟给30K,我的美团Offer终于来了
- 2025-05-14 java八股文(值得收藏)
- 2025-05-14 Android高级/资深面试题
- 2025-05-14 图解 LeetCode 算法汇总——链表
- 2025-05-14 Python 切片:让你轻松玩转序列数据的“魔法剪刀”
- 2025-05-14 数据结构:单链表算法题,常见技巧套路心得分享
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- nginx反向代理 (57)
- nginx日志 (56)
- nginx限制ip访问 (62)
- mac安装nginx (55)
- java和mysql (59)
- java中final (62)
- win10安装java (72)
- java启动参数 (64)
- java链表反转 (64)
- 字符串反转java (72)
- java逻辑运算符 (59)
- java 请求url (65)
- java信号量 (57)
- java定义枚举 (59)
- java字符串压缩 (56)
- java中的反射 (59)
- java 三维数组 (55)
- java插入排序 (68)
- java线程的状态 (62)
- java异步调用 (55)
- java中的异常处理 (62)
- java锁机制 (54)
- java静态内部类 (55)
- java怎么添加图片 (60)
- java 权限框架 (55)
本文暂时没有评论,来添加一个吧(●'◡'●)