一、底层数据结构
特性 | ArrayList | LinkedList |
---|---|---|
实现方式 | 基于动态数组 | 基于双向链表 |
内存布局 | 连续内存块,支持快速随机访问 | 离散节点,每个节点包含数据及前后指针 |
默认初始容量 | 10(扩容时增长 50%) | 无预分配容量,动态添加节点 |
二、核心操作性能对比
// ArrayList的随机访问示例
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
int val1 = arrayList.get(0); // O(1)// LinkedList的顺序访问示例
LinkedList<Integer> linkedList = new LinkedList<>();
linkedList.add(1);
int val2 = linkedList.get(0); // O(n)
操作 | ArrayList 时间复杂度 | LinkedList 时间复杂度 |
---|---|---|
随机访问(get/set) | O(1) | O(n) |
头部插入/删除 | O(n)(需移动元素) | O(1) |
尾部插入/删除 | 分摊 O(1)(无扩容时 O(1)) | O(1) |
中间插入/删除 | O(n) | O(n)(需遍历到目标位置) |
三、内存与 GC 影响
维度 | ArrayList | LinkedList |
---|---|---|
内存占用 | 仅存储元素 + 数组头(内存紧凑) | 每个节点额外存储两个指针(对象头 + 前后引用) |
GC 压力 | 整体回收高效(单个数组对象) | 频繁增删产生大量小对象,增加 GC 负担 |
缓存局部性 | 高(连续内存,CPU 预加载命中率高) | 低(节点分散,缓存未命中率高) |
四、扩容机制
-
ArrayList 扩容流程:
// 扩容核心逻辑(JDK17) int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍扩容 elementData = Arrays.copyOf(elementData, newCapacity);
- 代价:数据复制导致 O(n) 时间复杂度
- 优化建议:初始化时预估容量(
new ArrayList<>(initialCapacity)
)
-
LinkedList 无扩容:动态添加节点,但每个节点额外占用 24 字节(64 位 JVM)
五、线程安全与并发方案
方案 | ArrayList | LinkedList |
---|---|---|
默认线程安全 | 否 | 否 |
同步包装类 | Collections.synchronizedList() | Collections.synchronizedList() |
高并发替代方案 | CopyOnWriteArrayList | ConcurrentLinkedDeque |
六、工程实践场景
1. 电商商品列表展示
-
选择 ArrayList:
- 高频读取商品信息(随机访问)
- 批量更新时通过尾插法优化(
addAll()
)
List<Product> products = new ArrayList<>(10000); // 预分配容量
2. 实时消息队列
-
选择 LinkedList:
- 高频头尾操作(
addFirst()
/removeLast()
) - 使用迭代器避免随机访问:
LinkedList<Message> queue = new LinkedList<>(); // 生产者 queue.offer(new Message()); // 消费者(高效遍历) Iterator<Message> it = queue.iterator(); while(it.hasNext()) process(it.next());
- 高频头尾操作(
3. 多线程日志处理器
-
选择 CopyOnWriteArrayList:
- 写操作极少(日志初始化配置)
- 高频遍历读取日志规则
CopyOnWriteArrayList<LogRule> rules = new CopyOnWriteArrayList<>(); // 写操作(仅在配置更新时触发) rules.add(new LogRule()); // 读操作(无锁并发) rules.forEach(LogService::applyRule);
七、性能对比测试数据
测试环境:JDK17,10 万次操作,i7-11800H
测试场景 | ArrayList 耗时 | LinkedList 耗时 | 差异原因 |
---|---|---|---|
随机访问 1 万次 | 2ms | 650ms | 数组 O(1) vs 链表 O(n) 遍历 |
尾部插入 1 万次 | 3ms | 5ms | 均摊 O(1),链表节点创建开销略高 |
头部插入 1 万次 | 420ms | 8ms | 数组需移动元素,链表直接修改指针 |
遍历所有元素 | 15ms | 18ms | 数组缓存命中率高 |
八、高级特性对比
特性 | ArrayList | LinkedList |
---|---|---|
实现接口 | List | List + Deque |
序列化效率 | 高(连续数据,可批量写入) | 低(需逐个节点处理) |
内存池兼容性 | 适合对象池化(数组整体复用) | 节点分散,池化效果差 |
批量操作优化 | System.arraycopy() 高效 | 需要逐个节点操作 |
九、选型决策树
通过以上对比,开发者可根据具体场景选择最合适的实现:
- 优先 ArrayList:适用于 90% 的常规场景(读多写少、内存敏感)
- 慎用 LinkedList:仅在需要频繁头尾操作或实现双端队列时选用
- 线程安全场景:根据写频率选择
CopyOnWriteArrayList
或同步包装类