【java基础内容】ArrayList与LinkedList的区别及ArrayList源码解析

前言:

[ArrayList 与 LinkedList 的核心区别](#ArrayList 与 LinkedList 的核心区别)

[ArrayList 源码深度解析](#ArrayList 源码深度解析)

构造方法:初始化时到底有没有创建10个大小的数组?

情况一:无参构造

情况二:指定容量构造

[核心方法:add(E e) 与扩容机制](#核心方法:add(E e) 与扩容机制)

第一步:计算容量

第二步:判断是否需要扩容

[第三步:核心扩容逻辑 grow()](#第三步:核心扩容逻辑 grow())

如何选择

总结


前言:

在Java开发中,List接口是最常用的集合接口之一,而ArrayListLinkedList则是它的两个核心实现类。很多初学者甚至工作多年的开发者,往往只知道"ArrayList查询快,增删慢LinkedList增删快,查询慢"这句口诀。

本文将摒弃空谈 ,从底层数据结构源码实现 以及性能分析 三个维度,彻底搞清楚这两者的区别,并重点深入剖析ArrayList的源码,看看它究竟是如何工作的。

ArrayList 与 LinkedList 的核心区别

在开始看源码之前,我们先通过一张表快速建立直观的认知:

维度 ArrayList LinkedList
底层数据结构 动态数组 双向链表
内存占用 连续内存空间,可能浪费尾部空间 非连续内存,每个节点需存储前后指针,内存占用更大
随机访问 O(1),效率极高 O(n),需遍历链表,效率低
插入/删除 O(n),涉及数组元素的复制移动 O(n) ,虽然找到位置需要遍历,但操作本身只需修改指针指向;若在头尾操作,效率极高为O(1)
尾部插入 O(1) (均摊),需考虑扩容 O(1)
迭代效率 较高,对CPU缓存友好 较低,节点在内存中分散,缓存命中率低

比较他们不同的时候从,底层结构,性能,内存,线程安全来说。他们线程都不安全。

一句话总结:

绝大多数业务场景(以遍历和随机访问为主),ArrayList 完胜 LinkedList。只有在频繁头部插入/删除且数据量极大时,LinkedList 才有理论上的优势。

ArrayList 源码深度解析

环境基于 JDK 1.8

java 复制代码
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    // 默认初始容量
    private static final int DEFAULT_CAPACITY = 10;
    
    // 空数组实例,用于构造方法指定初始容量为0时
    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    // 默认容量的空数组实例,用于无参构造,用于区分第一次添加元素时的扩容逻辑
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    // 真正存储元素的数组,使用 transient 修饰,不参与默认序列化
    transient Object[] elementData;
    
    // 集合中实际元素个数
    private int size;
}

构造方法:初始化时到底有没有创建10个大小的数组?

这是一个经典的面试题。

情况一:无参构造
java 复制代码
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

此时,elementData 指向的是一个空数组,并没有初始化容量为10。延迟加载(懒加载)策略,等到第一次 add 时才真正分配空间。

情况二:指定容量构造
java 复制代码
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
    }
}

可以看见,如果指定了大于0的数,那么就会直接创造指定大小的数组,如果指定容量为 10,则会立即创建一个长度为 10 的数组。

核心方法:add(E e) 与扩容机制

这是 ArrayList 最精髓的部分。

java 复制代码
public boolean add(E e) {
    // 1. 确保内部容量足够(核心扩容逻辑)
    ensureCapacityInternal(size + 1);  // 传入最小需要容量 minCapacity = size + 1
    // 2. 赋值
    elementData[size++] = e;
    return true;
}
第一步:计算容量
java 复制代码
private void ensureCapacityInternal(int minCapacity) {
    // 如果当前数组是默认的空数组(无参构造创建的)
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 取 DEFAULT_CAPACITY (10) 和 minCapacity 的最大值
        // 第一次 add 时,minCapacity = 1,所以这里会返回 10
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    // 进入真正的扩容判断
    ensureExplicitCapacity(minCapacity);
}
第二步:判断是否需要扩容
java 复制代码
private void ensureExplicitCapacity(int minCapacity) {
    modCount++; // 记录结构修改次数,用于快速失败机制(fail-fast)

    // 如果需要的容量 大于 当前数组的长度,则必须扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
第三步:核心扩容逻辑 grow()
java 复制代码
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    // 关键点:新容量 = 旧容量 + 旧容量 >> 1 (即原来的 1.5 倍)
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    
    // 如果扩容 1.5 倍后还是小于最小需求容量(这种情况极少,发生在初始容量为0或1时)
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    
    // 如果新容量超过了规定的最大数组容量 (Integer.MAX_VALUE - 8)
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    
    // 核心:数组复制,产生一个新的数组,将原数据拷贝过去
    elementData = Arrays.copyOf(elementData, newCapacity);
}

扩容机制总结:

  1. 触发时机 :当**size + 1** 大于当前数组长度时。

  2. 扩容大小 :变为原来的 1.5 倍oldCapacity + (oldCapacity >> 1))。

  3. 性能影响 :扩容涉及 Arrays.copyOf,这是一个耗时的 O(n) 操作。如果能预估数据量,使用 new ArrayList<>(int capacity) 指定大小,可以避免频繁扩容,显著提升性能。

如何选择

优先使用 ArrayList :在绝大多数业务开发中(展示列表、查询数据、批量插入后仅遍历),ArrayList 是唯一正确的选择。

  1. 使用 LinkedList 的场景

    • 实现 队列(Queue)双端队列(Deque) 结构,利用 offerpollpeek 等 API。

    • 需要频繁在列表头部进行插入或删除,且数据量极大(百万级以上)。

  2. 避免的错误

    • 不要使用 LinkedList 存储大量数据仅仅因为你需要偶尔的中间删除。

    • 使用 ArrayList 时,如果能预估数据量,务必指定初始容量,避免频繁扩容带来的性能损耗。

总结

底层结构ArrayList 是数组,LinkedList 是双向链表。

性能ArrayList 查询快,中间增删慢;LinkedList 查询慢,头尾增删快。

源码重点ArrayList扩容机制 是 1.5 倍,无参构造延迟初始化。

场景 :开发中 90% 以上的场景用 ArrayList ,只有在需要实现队列或者极端高频的头尾操作时,才考虑 LinkedList

相关推荐
Charlie_lll3 小时前
力扣解题-67. 二进制求和
算法·leetcode·职场和发展
程序员三藏3 小时前
软件测试:白盒测试详解
自动化测试·软件测试·python·测试工具·职场和发展·单元测试·测试用例
東雪木3 小时前
Java学习——重载 (Overload) 与重写 (Override) 的核心区别、底层实现规则
java·开发语言·jvm·学习·java面试
爱丽_3 小时前
JVM GC 调优:内存指标、泄漏排查与线上自救
java·开发语言·jvm
AI自动化工坊3 小时前
OpenFang实战指南:用Rust构建高并发AI Agent操作系统
开发语言·人工智能·ai·rust·agent·ai agent
一只叫煤球的猫3 小时前
芋道源码,拉黑我,改变不了你还在搬运别人文章的事实
java·后端·面试
天才测试猿3 小时前
接口自动化测试详解
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·接口测试