目录
- [1. 基本概述与对比](#1. 基本概述与对比)
- [2. ArrayList详解](#2. ArrayList详解)
- [3. LinkedList详解](#3. LinkedList详解)
- [4. 性能对比分析](#4. 性能对比分析)
- [5. 最佳实践](#5. 最佳实践)
1. 基本概述与对比
1.1 整体对比
| 特性 | ArrayList | LinkedList | 
|---|---|---|
| 底层实现 | 动态数组 | 双向链表 | 
| 随机访问 | O(1) | O(n) | 
| 插入删除 | O(n) | O(1) | 
| 内存占用 | 连续内存空间 | 零散内存空间 | 
| 默认容量 | 10 | 无初始容量 | 
1.2 数据结构示意图
ArrayList结构:
            
            
              plain
              
              
            
          
          ┌─────┬─────┬─────┬─────┬─────┐
│  0  │  1  │  2  │  3  │  4  │
└─────┴─────┴─────┴─────┴─────┘LinkedList结构:
            
            
              plain
              
              
            
          
          ┌───┐    ┌───┐    ┌───┐
│ 1 │<-->│ 2 │<-->│ 3 │
└───┘    └───┘    └───┘2. ArrayList详解
2.1 核心属性
            
            
              java
              
              
            
          
          public class ArrayList<E> extends AbstractList<E> {
    // 默认初始容量
    private static final int DEFAULT_CAPACITY = 10;
    
    // 存储元素的数组
    transient Object[] elementData;
    
    // 实际元素数量
    private int size;
    
    // 用于空实例的共享空数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    // 用于默认大小的空实例的共享数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
}2.2 动态扩容机制
ArrayList最关键的特性是动态扩容。当数组空间不足时,会创建更大的数组并复制元素。
            
            
              java
              
              
            
          
          private void grow(int minCapacity) {
    // 获取旧容量
    int oldCapacity = elementData.length;
    // 新容量 = 旧容量 + 旧容量/2 (增加50%)
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    
    // 如果新容量仍小于需要的容量,直接使用需要的容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
        
    // 如果超过最大数组大小,使用最大值
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
        
    // 创建新数组并复制元素
    elementData = Arrays.copyOf(elementData, newCapacity);
}扩容过程示意图:
            
            
              plain
              
              
            
          
          原数组: [1,2,3,4,5] (容量5)
↓ 添加元素触发扩容
新数组: [1,2,3,4,5,_,_] (容量7)2.3 核心方法实现
2.3.1 添加元素
            
            
              java
              
              
            
          
          public boolean add(E e) {
    // 确保容量足够
    ensureCapacityInternal(size + 1);
    // 将元素添加到数组末尾
    elementData[size++] = e;
    return true;
}
public void add(int index, E element) {
    // 检查索引是否合法
    rangeCheckForAdd(index);
    // 确保容量足够
    ensureCapacityInternal(size + 1);
    // 将index后的元素向后移动一位
    System.arraycopy(elementData, index, 
                     elementData, index + 1,
                     size - index);
    // 在index位置插入新元素
    elementData[index] = element;
    size++;
}2.3.2 删除元素
            
            
              java
              
              
            
          
          public E remove(int index) {
    // 检查索引是否合法
    rangeCheck(index);
    
    modCount++;
    // 获取要删除的元素
    E oldValue = elementData(index);
    
    // 计算要移动的元素个数
    int numMoved = size - index - 1;
    if (numMoved > 0)
        // 将后面的元素向前移动一位
        System.arraycopy(elementData, index+1, 
                         elementData, index,
                         numMoved);
    // 清除最后一个元素的引用                     
    elementData[--size] = null;
    
    return oldValue;
}3. LinkedList详解
3.1 核心属性和节点结构
            
            
              java
              
              
            
          
          public class LinkedList<E> {
    // 链表大小
    transient int size = 0;
    // 头节点
    transient Node<E> first;
    // 尾节点
    transient Node<E> last;
    
    // 节点类定义
    private static class Node<E> {
        E item;           // 元素值
        Node<E> next;     // 后继节点
        Node<E> prev;     // 前驱节点
        
        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
}3.2 核心方法实现
3.2.1 添加元素
            
            
              java
              
              
            
          
          public boolean add(E e) {
    // 在链表末尾添加节点
    linkLast(e);
    return true;
}
void linkLast(E e) {
    // 保存原尾节点
    final Node<E> l = last;
    // 创建新节点
    final Node<E> newNode = new Node<>(l, e, null);
    // 更新尾节点
    last = newNode;
    // 如果原尾节点为空,说明是空链表
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}添加过程示意图:
            
            
              plain
              
              
            
          
          原链表: A <-> B
新元素: C
结果: A <-> B <-> C3.2.2 删除元素
            
            
              java
              
              
            
          
          public E remove(int index) {
    // 检查索引是否合法
    checkElementIndex(index);
    // 删除并返回指定位置的节点
    return unlink(node(index));
}
E unlink(Node<E> x) {
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;
    
    // 处理前驱节点
    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }
    
    // 处理后继节点
    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }
    
    x.item = null;
    size--;
    modCount++;
    return element;
}4. 性能对比分析
4.1 时间复杂度对比
| 操作 | ArrayList | LinkedList | 说明 | 
|---|---|---|---|
| add(E) | O(1) | O(1) | 平均情况 | 
| add(int, E) | O(n) | O(n) | 需要移动元素/遍历到指定位置 | 
| remove(int) | O(n) | O(n) | 需要移动元素/遍历到指定位置 | 
| get(int) | O(1) | O(n) | 数组支持随机访问 | 
| contains(Object) | O(n) | O(n) | 都需要遍历 | 
4.2 内存占用对比
ArrayList:
- 需要连续的内存空间
- 扩容时需要额外空间
- 每个元素占用空间小
LinkedList:
- 不需要连续内存空间
- 不需要预留空间
- 每个元素需要额外的前后指针空间
4.3 适用场景对比
ArrayList适用于:
- 频繁随机访问的场景
- 元素数量相对固定的场景
- 对内存空间要求高的场景
LinkedList适用于:
- 频繁在任意位置插入、删除的场景
- 元素数量变化较大的场景
- 不需要随机访问的场景
5. 最佳实践
5.1 ArrayList最佳实践
- 初始容量设置
            
            
              java
              
              
            
          
          // 如果知道大概容量,指定初始值避免扩容
ArrayList<String> list = new ArrayList<>(10000);- 批量操作优化
            
            
              java
              
              
            
          
          // 使用ensureCapacity预分配空间
list.ensureCapacity(10000);
for(int i = 0; i < 10000; i++) {
    list.add(i);
}- 删除元素优化
            
            
              java
              
              
            
          
          // 从后向前删除,避免元素移动
for(int i = list.size() - 1; i >= 0; i--) {
    if(condition) {
        list.remove(i);
    }
}5.2 LinkedList最佳实践
- 优化遍历操作
            
            
              java
              
              
            
          
          // 使用迭代器遍历
Iterator<String> iter = list.iterator();
while(iter.hasNext()) {
    String item = iter.next();
    if(condition) {
        iter.remove(); // 使用迭代器的remove方法
    }
}- 合理使用双向特性
            
            
              java
              
              
            
          
          // 如果要访问的位置靠近尾部,可以从尾部开始遍历
if(index > (size >> 1)) {
    Node<E> x = last;
    for(int i = size - 1; i > index; i--)
        x = x.prev;
    return x;
}5.3 选择建议
- 根据实际场景选择合适的List实现:
- 如果不确定,优先使用ArrayList
- 如果频繁插入删除,考虑LinkedList
- 如果需要随机访问,必须使用ArrayList
- 注意避免的做法:
- 不要在ArrayList中间频繁插入删除
- 不要在LinkedList中频繁随机访问
- 不要频繁扩容ArrayList
总结
- ArrayList和LinkedList各有优势:
- ArrayList: 随机访问快,内存占用少
- LinkedList: 插入删除快,空间利用灵活
 
- 核心区别:
- 底层数据结构不同
- 操作效率侧重点不同
- 内存占用模式不同
 
- 使用建议:
- 根据实际场景选择合适的实现
- 关注性能关键点
- 遵循最佳实践
 
通过深入理解这两种List的实现原理和特点,可以在实际开发中做出更好的选择,写出更高效的代码。