JDK源码解析:ArrayList

1、背景

我们咨询一下阿里通义大模型,什么是“LinkedList”。

2a8fd4ad67c75476ad083642b6ae9d8d.png

以下是通义大模型的回答:

ArrayList 是 Java 编程语言中的一种动态数组实现,它是 java.util 包中的一个类。
ArrayList 实现了 List 接口,能够存储元素的有序集合,并且允许插入重复项。


与传统的固定大小的数组不同,ArrayList 的大小可以根据需要动态增长或缩小。当添加新的元素时,如果当前容量不足以容纳新元素,
ArrayList 会自动调整其内部存储空间的大小以适应更多的元素。

以下是一些 ArrayList 的基本特点和方法:

  • 动态大小:ArrayList 的大小不是固定的,可以随着元素的添加和移除而改变。

  • 索引访问:可以通过索引来访问或修改列表中的任何元素。

  • 插入和删除操作:可以在列表的任意位置插入或删除元素。

  • 常用方法:

    • add(E e):在列表末尾添加一个元素。

    • remove(Object o):从列表中移除第一次出现的指定元素。

    • get(int index):返回指定索引处的元素。

    • set(int index, E element):将指定位置的元素替换成另一个元素。

    • size():返回列表中的元素数量。

    • isEmpty():检查列表是否为空。

    • clear():清空列表中的所有元素。

    • contains(Object o):检查列表是否包含指定元素。

    • indexOf(Object o):返回列表中指定元素首次出现的位置。

    • lastIndexOf(Object o):返回列表中指定元素最后一次出现的位置。

2、源码分析

类定义

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{}

代码分析:

  • ArrayList<E>: 这是类名,其中 <E> 表示这是一个泛型类,E 代表元素类型。这意味着 ArrayList 可以用来存储任何类型的对象,具体类型由使用者指定。

  • AbstractList 提供了一些通用的实现细节,比如基本的迭代器实现和其他辅助方法,这些方法对于所有的 List 实现都是有用的。

  • RandomAccess: 这是一个标记接口,用于指示该列表支持快速随机访问。这使得某些算法(如排序)可以采用更高效的实现方式。

基本属性

// 默认容量大小
private static final int DEFAULT_CAPACITY = 10;


// 空长度数组
private static final Object[] EMPTY_ELEMENTDATA = {};


// 区别于EMPTY_ELEMENTDATA,两者使用场景不同
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};


// buffer数组
transient Object[] elementData; 


// buffer数组的数据长度
private int size;


// 
protected transient int modCount = 0;


// 指定容量大小
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);
    }
}


// 没指定容量大小
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

代码分析:

  1. DEFAULT_CAPACITY:默认容器长度为10

  2. EMPTY_ELEMENTDATA:这是一个空的数组,用于表示一个空的 ArrayList。当 ArrayList 初始容量为 0 时,会使用这个数组。当 ArrayList 中的元素被清空后,也会将内部数组设置为 EMPTY_ELEMENTDATA。这样做的好处是避免了创建新的空数组对象,从而节省内存。

  3. DEFAULTCAPACITY_EMPTY_ELEMENTDATA:这是一个空的数组

    1. 与 EMPTY_ELEMENTDATA 的区别在于,当 ArrayList 使用这个数组时,它的初始容量是默认值(即 10)。

    2. 当 ArrayList 刚创建时,如果指定了初始容量为 0,那么会使用 EMPTY_ELEMENTDATA

    3. 如果没有指定初始容量或者指定了一个大于 0 的值,那么会使用 DEFAULTCAPACITY_EMPTY_ELEMENTDATA

    4. 这样做的好处是避免了在创建 ArrayList 时立即分配内存,从而提高了性能。

    5. 两者都是为了提高 ArrayList 的性能而设计的。它们在不同的场景下被使用,以确保内存使用的高效性。

  4. size:数组真实长度,充当数组索引下标,快速获取数据

  5. modCount:线程不安全,提供快速失败机制

基本操作:增删改查

(1)增加元素

5d6f5c6f731e820ce695d3b8d30b7899.png

解析add(E)方法源码

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}


private void add(E e, Object[] elementData, int s) {
    // 【1】
    if (s == elementData.length)
        // 【2】
        elementData = grow();
    // 【3】
    elementData[s] = e;
    // 【4】
    size = s + 1;
}


private Object[] grow() {
    // 【2.1】快速扩容
    return grow(size + 1);
}


private Object[] grow(int minCapacity) {
    // 【2.2】拷贝至新数组里
    return elementData = 、
        Arrays.copyOf(elementData, newCapacity(minCapacity));
}


private int newCapacity(int minCapacity) {
    // 数据溢出大小
    int oldCapacity = elementData.length;
    // 【2.3】旧的容量,扩容1/2
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 【2.4】如果扩容后的结果,还是不够
    if (newCapacity - minCapacity <= 0) {
        // 【2.5】此前没有指定容量大小
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // 【2.5】返回动态最大容量
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        if (minCapacity < 0) 
            // 【2.6】内存溢出
            throw new OutOfMemoryError();
        // 【2.7】返回最小容量
        return minCapacity;
    }
    // 【2.8】正常情况(正常返回最大容量大小)
    return (newCapacity - MAX_ARRAY_SIZE <= 0)
        ? newCapacity
        : hugeCapacity(minCapacity);
}

分析:

  1. 【1】如果插入下表,等于数据长度,说明容器数组已经满了,要扩容grow

    1. 【2.5】如果

      elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,

      说明ArrayList并没有被初始化过,那么则返回需要初始化的数据长度最大值(DEFAULT_CAPACITY, minCapacity)

    2. 【2.6】minCapacity小于0,说明内存溢出了

    3. 【2.7】极端情况,返回minCapacity

    4. 【2.1】扩容最小容量为size+1,传入grow方法

    5. 【2.3】计算newCapacity:扩容的增量大小为一半

    6. 【2.4】扩容后还是比 最小容量 要小或者刚刚好

    7. 【2.8】如果newCapacity> 最小容量,则说明扩容有效,扩容后的长度,最大不超过MAX_ARRAY_SIZE

(2)删除元素

a526d35b2f337da4775e97c4090d5f68.png

解析remove(int)方法源码

public E remove(int index) {
    // 【1】
    Objects.checkIndex(index, size);
    final Object[] es = elementData;


    @SuppressWarnings("unchecked") E oldValue = (E) es[index];
    // 【2】
    fastRemove(es, index);


    return oldValue;
}


private void fastRemove(Object[] es, int i) {
    //【3】
    modCount++;
    final int newSize;
    // 【4】
    if ((newSize = size - 1) > i)
        //【5】
        System.arraycopy(es, i + 1, es, i, newSize - i);
    // 【6】
    es[size = newSize] = null;
}

代码分析:

  1. 【1】检查下标是否超限

  2. 【2】快速移除

    1. 【5】如果不是(i数值,比真实长度数值,小),直接把i+1后的(newSize - i)个元素,从i下标开始拷贝一份

    2. 【6】如果是,则直接把尾部元素,设置为null

    3. 【3】操作总数+1

    4. 【4】newSize赋值;比较:i和newSize大小 -> 判断是否是移除尾部元素

(3)修改元素

ab66806b3aaea0b4e11e0894e257a654.png

解析set(int index, E element)

public E set(int index, E element) {
    Objects.checkIndex(index, size);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}


E elementData(int index) {
    return (E) elementData[index];
}

源码分析:

  • 设置index下标的数据,时间复杂度为O(1)

  • 返回老数据


(4)获取元素

bed60d92c62caf2492accf7065bdd261.png

解析get(int)

public E get(int index) {
    Objects.checkIndex(index, size);
    return elementData(index);
}
E elementData(int index) {
    return (E) elementData[index];
}

源码分析:

  • 直接通过数组下表返回,时间复杂度为O(1)

3、总结

使用 ArrayList 可以非常方便地处理数据集合,尤其是在需要频繁添加或删除元素的情况下。由于它的灵活性和便利性,ArrayList 在实际开发中被广泛使用。

我们可以对比跟之前的数据结构LinkedList 链表,得出两者差异。

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值