ArrayList底层原理(详细源码分析)

本文深入剖析了ArrayList的内部实现机制,包括其初始化过程、数组扩容原理、基本操作方法如add、remove、get和set等,并解释了ArrayList在不同场景下的性能表现。

ArrayList是一种基于定长数组实现的变长集合类,线程不安全,易查询,难增删

源码分析

ArrayList全局属性

定义了初始容量10

使用默认构造方法初始化出来的容量是 10(1.7 之后都是延迟初始化,即第一次调用 add 方法添加元素的时候才将elementData 容量初始化为 10)。

 private static final int DEFAULT_CAPACITY = 10;

定义了2个大小为0的数组

private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

声明了一个数组

transient修饰不被序列化

transient Object[] elementData;

定义了size变量

标识数组大小

private int size;

ArrayList构造方法

ArrayList无参构造

将之前声明创建的大小为0的数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋给elementData数组

public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

ArrayList有参构造

传入一个int类型的变量,指定使用ArrayList的容量。如果传入的参数initialCapacity大于0,则新建一个initialCapacity大小的数组。传入参数值等于0的话,将之前创建的EMPTY_ELEMENTDATA空数组给elementData。

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

ArrayList方法

add方法

add方法中调用了ensureCapacityInternal方法  并将size+1作为参数传入

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

 ensureCapacityInternal方法

首先调用了calculateCapacity方法作为参数传入

private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

calculateCapacity方法

判断当前数组是否为之前定义的空数组,

若为空则返回默认容量10和传入参数的最大值

若不为空则直接返回传入的数值,然后返回ensureExplicitCapacity方法

private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

ensureExplicitCapacity方法

modCount是ArrayList继承抽象父类AbstractList中的一条属性,初始为0

protected transient int modCount = 0;

 首先进行判断,如果传入的数组长度减去数组原先的长度大于0,我们则调用grow方法(扩容方法),并将新长度传入进去。

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

grow方法

这段代码就是扩容的核心,首先看前两行代码定义了旧长度和新长度

新长度=旧长度+旧长度>>1(右移运算) 等同于 新长度=旧长度+0.5旧长度=1.5旧长度,所以ArrayList底层数组每次扩容时都是先将原数组长度的1.5倍与传入的数组长度进行比较。

接下来进入if判断

第一个if,如果新长度小于传入的数组长度,那么就更新newCapacity(新长度)的值为minCapacity(传入长度)

第二个if,如果新数长度大于最大的数组长度,会进入hugeCapacity方法再次进行判断,返回Integer的最大长度,或者Integer的最大长度-8的长度。

最后通过Arrays.copyOf方法进行数组扩容

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

 hugeCapacity方法

Integer最大值

@Native public static final int   MAX_VALUE = 0x7fffffff;

arraylist数组长度最大值

为什么是Integer最大长度-8而不是-7或者-6呢?

数组类型是由jvm从元素类型合成出来的;在jvm中获取数组的长度是用arraylength这个专门的字节码指令的,在数组的对象头里有一个_length字段,记录数组长度,只需要去读_length字段,所以ArrayList中定义的最大长度为Integer最大值减8,这个8就是就是存了数组_length字段。Integer是int类型的包装类,而int类型在内存中占8bit

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

remove方法

删除是先判断删除的数组坐标是否越界,然后使用System.arraycopy进行数组复制,然后让旧的elementData最后一位置为null进行GC垃圾清理

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; // clear to let GC do its work

        return oldValue;
    }

get方法

get方法较为简单,判断是否越界后直接返回数组

public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

set方法

set方法也很简单,判断是否越界后,取旧值,再赋新值,最后返回旧值

public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

<think>我们被要求回答关于Java ArrayList底层实现原理。根据引用[3],我们知道ArrayList的核心是基于动态数组实现的,具有动态扩容能力,支持高效随机访问(O(1)),但插入和删除可能需要移动元素(O(n))。同时,引用[3]提供了源码的关键字段和核心机制。 回答结构: 1. 介绍ArrayList的基本概念和特点。 2. 底层数据结构:数组(elementData)和关键字段(size, 默认容量等)。 3. 核心机制:初始化、动态扩容、添加元素、删除元素等。 4. 线程安全性问题(引用[2]提到是非线程安全的,以及如何实现线程安全)。 5. 迭代器注意事项(引用[4]提到modCount机制,用于快速失败fail-fast)。 6. 性能分析(随机访问、插入、删除的时间复杂度)。 同时,根据系统指令,我们需要: - 行内数学表达式用$...$,例如:时间复杂度$O(1)$。 - 独立公式单独成段用$$...$$(但这里可能用不到复杂公式)。 - 使用中文。 - 生成相关问题。 另外,在回答中引用的段落末尾添加引用标识,如[^1]。 注意:引用[1]和[5]似乎与ArrayList关系不大,引用[1]是介绍ArrayList的简单性,引用[5]是关于HashMap的get方法,所以我们主要参考引用[2][3][4]。 开始回答:</think>### Java ArrayList 底层实现原理详解 #### 1️⃣ **核心数据结构** - **动态数组**:底层使用 `Object[] elementData` 存储元素(JDK 11 源码)[^3]。 - **关键字段**: - `transient Object[] elementData`:实际存储数据的数组(`transient` 避免序列化空位)。 - `private int size`:记录当前元素数量。 - `DEFAULT_CAPACITY = 10`:默认初始容量(首次添加元素时生效)。 #### 2️⃣ **四大核心机制** 1. **初始化** - **无参构造**:延迟初始化,创建空数组 `elementData = {}`。 - **首次添加元素**:若数组为空,扩容至默认容量 10[^3]。 ```java public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; // 空数组 } ``` 2. **动态扩容(核心)** - **触发条件**:添加元素时 `size == elementData.length`。 - **扩容规则**: - 新容量 = 旧容量 × 1.5(位运算优化:`newCapacity = oldCapacity + (oldCapacity >> 1)`)。 - 首次扩容从 0 → 10,后续按 1.5 倍增长(如 10→15→22)[^3]。 - **数据迁移**:`System.arraycopy()` 复制旧数组到新数组。 3. **添加元素** - **尾部插入**:直接赋值 `elementData[size++] = e`,时间复杂度 $O(1)$。 - **中间插入**:需移动后续元素(`System.arraycopy`),时间复杂度 $O(n)$。 4. **删除元素** - **尾部删除**:直接 `size--`,时间复杂度 $O(1)$。 - **中间删除**:需移动后续元素填补空位,时间复杂度 $O(n)$。 ```java public E remove(int index) { E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); // 移动元素 elementData[--size] = null; // 释放引用,帮助GC return oldValue; } ``` #### 3️⃣ **关键特性** - **随机访问高效**:通过索引直接访问数组元素,时间复杂度 $O(1)$。 - **插入/删除成本高**:需移动元素,最坏 $O(n)$。 - **线程不安全**:多线程操作需同步(如 `Collections.synchronizedList`)[^2]。 - **快速失败机制(Fail-Fast)**:迭代器通过 `modCount` 检测并发修改,抛出 `ConcurrentModificationException`[^4]。 #### 4️⃣ **性能对比** | 操作 | 时间复杂度 | 说明 | |--------------|------------|--------------------------| | `get(index)` | $O(1)$ | 直接数组索引访问 | | `add(e)` | 均摊 $O(1)$| 尾部插入,可能触发扩容 | | `add(i,e)` | $O(n)$ | 需移动元素 | | `remove(i)` | $O(n)$ | 需移动元素 | #### 5️⃣ **设计思想** - **空间换时间**:预分配容量减少频繁扩容。 - **均摊复杂度**:扩容成本分摊到多次插入操作。 - **懒加载**:无参构造时延迟分配内存,减少资源占用。 --- ### 相关问题 1. **为什么 ArrayList 的扩容因子是 1.5 而不是 2?** > 1.5 倍平衡了内存浪费与扩容频率,避免 2 倍扩容可能导致的过量空间浪费[^3]。 2. **如何优化 ArrayList 的插入性能?** > 预分配足够容量(`new ArrayList<>(initialCapacity)`),避免中间插入。 3. **ArrayList 和 LinkedList 如何选择?** > 频繁随机访问用 ArrayList;频繁插入/删除用 LinkedList。 4. **为什么迭代过程中修改 ArrayList 会抛出异常?** > 迭代器通过 `modCount` 检测结构性修改,实现快速失败机制[^4]。 5. **ArrayList 的序列化为何自定义 `writeObject/readObject`?** > 跳过空元素(`transient elementData`),仅序列化有效数据[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值