当前位置: 首页 > article >正文

ArrayList 与 LinkedList 对比与源码解读

目录

  • 1. 基本概述与对比
  • 2. ArrayList详解
  • 3. LinkedList详解
  • 4. 性能对比分析
  • 5. 最佳实践

1. 基本概述与对比

1.1 整体对比

特性ArrayListLinkedList
底层实现动态数组双向链表
随机访问O(1)O(n)
插入删除O(n)O(1)
内存占用连续内存空间零散内存空间
默认容量10无初始容量

1.2 数据结构示意图

ArrayList结构:

┌─────┬─────┬─────┬─────┬─────┐
│  0  │  1  │  2  │  3  │  4  │
└─────┴─────┴─────┴─────┴─────┘

LinkedList结构:

┌───┐    ┌───┐    ┌───┐
│ 1 │<-->│ 2 │<-->│ 3 │
└───┘    └───┘    └───┘

2. ArrayList详解

2.1 核心属性

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最关键的特性是动态扩容。当数组空间不足时,会创建更大的数组并复制元素。

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);
}

扩容过程示意图:

原数组: [1,2,3,4,5] (容量5)
↓ 添加元素触发扩容
新数组: [1,2,3,4,5,_,_] (容量7)

2.3 核心方法实现

2.3.1 添加元素
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 删除元素
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 核心属性和节点结构

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 添加元素
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++;
}

添加过程示意图:

原链表: A <-> B
新元素: C
结果: A <-> B <-> C
3.2.2 删除元素
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 时间复杂度对比

操作ArrayListLinkedList说明
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最佳实践

  1. 初始容量设置
// 如果知道大概容量,指定初始值避免扩容
ArrayList<String> list = new ArrayList<>(10000);
  1. 批量操作优化
// 使用ensureCapacity预分配空间
list.ensureCapacity(10000);
for(int i = 0; i < 10000; i++) {
    list.add(i);
}
  1. 删除元素优化
// 从后向前删除,避免元素移动
for(int i = list.size() - 1; i >= 0; i--) {
    if(condition) {
        list.remove(i);
    }
}

5.2 LinkedList最佳实践

  1. 优化遍历操作
// 使用迭代器遍历
Iterator<String> iter = list.iterator();
while(iter.hasNext()) {
    String item = iter.next();
    if(condition) {
        iter.remove(); // 使用迭代器的remove方法
    }
}
  1. 合理使用双向特性
// 如果要访问的位置靠近尾部,可以从尾部开始遍历
if(index > (size >> 1)) {
    Node<E> x = last;
    for(int i = size - 1; i > index; i--)
        x = x.prev;
    return x;
}

5.3 选择建议

  1. 根据实际场景选择合适的List实现:
  • 如果不确定,优先使用ArrayList
  • 如果频繁插入删除,考虑LinkedList
  • 如果需要随机访问,必须使用ArrayList
  1. 注意避免的做法:
  • 不要在ArrayList中间频繁插入删除
  • 不要在LinkedList中频繁随机访问
  • 不要频繁扩容ArrayList

总结

  1. ArrayList和LinkedList各有优势:
    • ArrayList: 随机访问快,内存占用少
    • LinkedList: 插入删除快,空间利用灵活
  2. 核心区别:
    • 底层数据结构不同
    • 操作效率侧重点不同
    • 内存占用模式不同
  3. 使用建议:
    • 根据实际场景选择合适的实现
    • 关注性能关键点
    • 遵循最佳实践

通过深入理解这两种List的实现原理和特点,可以在实际开发中做出更好的选择,写出更高效的代码。


http://www.kler.cn/a/464515.html

相关文章:

  • CSS学习记录21
  • Java 数据库连接 - Sqlite
  • 【74HC192减法24/20/72进制】2022-5-17
  • Spring Boot 各种事务操作实战(自动回滚、手动回滚、部分回滚)
  • 智联视频超融合平台:电力行业的智能守护者
  • “AI智慧教学系统:开启个性化教育新时代
  • vue2实现excel文件预览
  • 鸿蒙应用开发搬砖经验之-ArkWeb加载页面的超简单示例
  • vue3 Suspense组件
  • 深入探究 Louvain 算法:从原理到实现
  • 电子电器架构 -- 什么是用于ADAS/AD系统的雷达?
  • JAVA创建绘图板JAVA构建主窗口鼠标拖动来绘制线条
  • 第二十五天 项目实践:图像分类
  • python学习笔记—12—
  • 设计模式 创建型 原型模式(Prototype Pattern)与 常见技术框架应用 解析
  • 闪测仪在医用人造骨骼尺寸检测中的革新应用——从2D到3D的全面升级
  • C语言中的隐式转换问题
  • 王老吉药业SRM系统上线 携手隆道共启战略合作新篇章
  • 【优选算法】查找总价格为目标值的两个商品(双指针)
  • Java-数据结构-包装类与泛型
  • YOLO11改进 | 卷积模块 | ECCV2024 小波卷积
  • 英文字体:创意前卫杀手级标题海报封面设计粗体字体 Morne Display
  • swiftui中struct该如何使用?可选字段怎么定义?使用Alamofire发送请求接收responseDecodable相应解析
  • 远场P2P穿越
  • Facebook元宇宙项目中的智能合约应用:提升虚拟空间的自治能力
  • 《探秘计算机视觉与深度学习:开启智能视觉新时代》