【集合源码系列】ArrayList源码解析

本文深入解析了ArrayList的内部实现,包括其数据结构、构造方法、添加元素(指定索引和不指定索引)、删除元素(指定索引和指定元素)以及数组扩容策略。同时,讨论了ArrayList的查询、删除和添加操作的时间复杂度。
摘要由CSDN通过智能技术生成
ArrrayList

在这里插入图片描述

  • java.util.RandomAccess 接口,表示 ArrayList 支持快速的随机访问
数据结构
transient Object[] elementData;

// 实际存储数据的多少
private int size;
构造方法
  • 指定大小
// 初始化长度为0时,给elementData赋值的空数组
private static final Object[] EMPTY_ELEMENTDATA = {};

public ArrayList(int initialCapacity) {
    // 初始化容量大于 0 时,创建 Object 数组
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    // 初始化容量等于 0 时,使用 EMPTY_ELEMENTDATA 对象
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    // 初始化容量小于 0 时,抛出 IllegalArgumentException 异常
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
  • 不指定大小
// 不指定大小时,默认第一次扩容的大小
private static final int DEFAULT_CAPACITY = 10;


// 未指定参数时,给elementData赋值的空数组
// 这里要和 EMPTY_ELEMENTDATA 区分开,因为初始化长度为0和未指定长度,在扩容时策略不同
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

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

添加元素

核心为调用System.arraycopy()

  • 不指定索引
@Override
public boolean add(E e) {
    // <1> 增加数组修改次数
    modCount++;
    // 添加元素
    add(e, elementData, size);
    // 返回添加成功
    return true;
}

private void add(E e, Object[] elementData, int s) {
    // <2> 如果容量不够,进行扩容
    if (s == elementData.length)
        elementData = grow();
    // <3> 设置到末尾
    elementData[s] = e;
    // <4> 数量大小加一
    size = s + 1;
}
  • 指定索引
public void add(int index, E element) {
    // 校验位置是否在数组范围内
    rangeCheckForAdd(index);
    // 增加数组修改次数
    modCount++;
    // 如果数组大小不够,进行扩容
    final int s;
    Object[] elementData;
    if ((s = size) == (elementData = this.elementData).length)
        elementData = grow();
    // 将 index + 1 位置开始的元素,进行往后挪
    System.arraycopy(elementData, index,
                     elementData, index + 1,
                     s - index);
    // 设置到指定位置
    elementData[index] = element;
    // 数组大小加一
    size = s + 1;
}

private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
  • 添加多个元素
public boolean addAll(Collection<? extends E> c) {
    // 转成 a 数组
    Object[] a = c.toArray();
    // 增加修改次数
    modCount++;
    // 如果 a 数组大小为 0 ,返回 ArrayList 数组无变化
    int numNew = a.length;
    if (numNew == 0)
        return false;
    // 如果 elementData 剩余的空间不够,则进行扩容。要求扩容的大小,至于能够装下 a 数组。
    Object[] elementData;
    final int s;
    if (numNew > (elementData = this.elementData).length - (s = size))
        elementData = grow(s + numNew);
    // 将 a 复制到 elementData 从 s 开始位置
    System.arraycopy(a, 0, elementData, s, numNew);
    // 数组大小加 numNew
    size = s + numNew;
    return true;
}
数组扩容
private Object[] grow() {
    return grow(size + 1);
}

private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    /**
     * 如果原容量大于 0 ,或者数组不是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA (即初始化未指定大小)时,计算新的数组大小,并创建扩容
     */
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        /**
         * 在 minCapacity - oldCapacity 和 oldCapacity >> 1(即oldCapacity的一半)中取一个大的,然后加到oldCapacity上
         */
        int newCapacity = ArraysSupport.newLength(oldCapacity,
                minCapacity - oldCapacity, /* minimum growth */
                oldCapacity >> 1           /* preferred growth */);
        // 将elementData改变到指定大小
        return elementData = Arrays.copyOf(elementData, newCapacity);
    /**
     * 如果是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 数组(即初始化未指定大小),直接创建新的数组即可。
     */
    } else {
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}
删除元素
  • 指定索引
public E remove(int index) {
    // 校验 index 不要超过 size
    Objects.checkIndex(index, size);
    final Object[] es = elementData;

    // 记录该位置的原值
    @SuppressWarnings("unchecked") E oldValue = (E) es[index];
    // 快速移除
    fastRemove(es, index);

    // 返回该位置的原值
    return oldValue;
}

private void fastRemove(Object[] es, int i) {
    // 增加数组修改次数
    modCount++;
    // 如果 i 不是移除最末尾的元素,则将 i + 1 位置的数组往前挪
    final int newSize;
    if ((newSize = size - 1) > i) // -1 的原因是,size 是从 1 开始,而数组下标是从 0 开始。
        System.arraycopy(es, i + 1, es, i, newSize - i);
    // 将新的末尾置为 null ,帮助 GC
    es[size = newSize] = null;
}
  • 指定元素
public boolean remove(Object o) {
    final Object[] es = elementData;
    final int size = this.size;
    // 寻找首个为 o 的位置
    int i = 0;
    found: {
        if (o == null) { // o 为 null 的情况
            for (; i < size; i++)
                if (es[i] == null)
                    break found;
        } else { // o 非 null 的情况
            for (; i < size; i++)
                if (o.equals(es[i]))
                    break found;
        }
        // 如果没找到,返回 false
        return false;
    }
    // 快速移除
    fastRemove(es, i);
    // 找到了,返回 true
    return true;
}
  • 删除指定索引范围
protected void removeRange(int fromIndex, int toIndex) {
    // 范围不正确,抛出 IndexOutOfBoundsException 异常
    if (fromIndex > toIndex) {
        throw new IndexOutOfBoundsException(
                outOfBoundsMsg(fromIndex, toIndex));
    }
    // 增加数组修改次数
    modCount++;
    // <X> 移除 [fromIndex, toIndex) 的多个元素
    shiftTailOverGap(elementData, fromIndex, toIndex);
}

private void shiftTailOverGap(Object[] es, int lo, int hi) {
    // 将 es 从 hi 位置开始的元素,移到 lo 位置开始。
    System.arraycopy(es, hi, es, lo, size - hi);
    // 将从 [size - hi + lo, size) 的元素置空,因为已经被挪到前面了。
    for (int to = size, i = (size -= hi - lo); i < to; i++)
        es[i] = null;
}

private static String outOfBoundsMsg(int fromIndex, int toIndex) {
    return "From Index: " + fromIndex + " > To Index: " + toIndex;
}
  • 删除多个元素
public boolean removeAll(Collection<?> c) {
    return batchRemove(c, false, 0, size);
}

boolean batchRemove(Collection<?> c, boolean complement, final int from, final int end) {
    // 校验 c 非 null 。
    Objects.requireNonNull(c);
    final Object[] es = elementData;
    int r;
    
    // 优化,顺序遍历 elementData 数组,找到第一个不符合 complement ,然后结束遍历。
    for (r = from;; r++) {
        // 遍历到尾,都没不符合条件的,直接返回 false 。
        if (r == end)
            return false;
        //  如果包含结果不符合 complement 时,结束
        if (c.contains(es[r]) != complement)
            break;
    }
    // 设置开始写入 w 为 r ,注意不是 r++ 。
    // r++ 后,用于读取下一个位置的元素。因为通过上的优化循环,我们已经 es[r] 是不符合条件的。
    int w = r++;
    try {
        // 继续遍历 elementData 数组,如何符合条件,则进行移除
        for (Object e; r < end; r++)
            if (c.contains(e = es[r]) == complement) // 判断符合条件
                // 移除的方式,通过将当前值 e 写入到 w 位置,然后 w 跳到下一个位置。
                es[w++] = e; 
    } catch (Throwable ex) {
        // 如果 contains 方法发生异常,则将 es 从 r 位置的数据写入到 es 从 w 开始的位置
        System.arraycopy(es, r, es, w, end - r);
        w += end - r;
        // 继续抛出异常
        throw ex;
    } finally {
        // 增加数组修改次数
        modCount += end - w;
        // 将数组 [w, end) 位置赋值为 null 。
        shiftTailOverGap(es, w, end);
    }
    return true;
}
清空
public void clear() {
    // 获得当前的数组修改次数
    modCount++;
    // 遍历数组,倒序设置为 null
    final Object[] es = elementData;
    for (int to = size, i = size = 0; i < to; i++)
        es[i] = null;
}
迭代器
  • 获取迭代器
public Iterator<E> iterator() {
    return new Itr();
}

Itr 一共有 3 个属性,如下:

// 下一个访问元素的位置,从下标 0 开始。
int cursor;   

/**
 * 上一次访问元素的位置。
 *
 * 1. 初始化为 -1 ,表示无上一个访问的元素
 * 2. 遍历到下一个元素时,lastRet 会指向当前元素,而 cursor 会指向下一个元素。这样,如果我们要实现 remove 方法,移除当前元素,就可以实现了。
 * 3. 移除元素时,设置为 -1 ,表示最后访问的元素不存在了,都被移除咧。
 */
int lastRet = -1; // index of last element returned; -1 if no such

/**
 * 创建迭代器时,数组修改次数。
 *
 * 在迭代过程中,如果数组发生了变化,会抛出 ConcurrentModificationException 异常。
 */
int expectedModCount = modCount;

// prevent creating a synthetic constructor
Itr() {}
  • 判断是否还有下一个元素
public boolean hasNext() {
    return cursor != size;
}
  • 获取下一个元素
public E next() {
    // 校验是否数组发生了变化
    checkForComodification();
    // 判断如果超过 size 范围,抛出 NoSuchElementException 异常
    // <1> i 记录当前 cursor 的位置
    int i = cursor; 
    if (i >= size)
        throw new NoSuchElementException();
    // 判断如果超过 elementData 大小,说明可能被修改了,抛出 ConcurrentModificationException 异常
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    // <2> cursor 指向下一个位置
    cursor = i + 1;
    // <3> 返回当前位置的元素
    return (E) elementData[lastRet = i]; // <4> 此处,会将 lastRet 指向当前位置
}

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}
  • 移除当前元素
public void remove() {
    // 如果 lastRet 小于 0 ,说明没有指向任何元素,抛出 IllegalStateException 异常
    if (lastRet < 0)
        throw new IllegalStateException();
    // 校验是否数组发生了变化
    checkForComodification();

    try {
        // 移除 lastRet 位置的元素
        ArrayList.this.remove(lastRet);
        // cursor 指向 lastRet 位置,因为被移了,所以需要后退下
        cursor = lastRet;
        // lastRet 标记为 -1 ,因为当前元素被移除了
        lastRet = -1;
        // 记录新的数组的修改次数
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}
  • 消费剩余未迭代的元素
@Override
public void forEachRemaining(Consumer<? super E> action) {
    // 要求 action 非空
    Objects.requireNonNull(action);
    // 获得当前数组大小
    final int size = ArrayList.this.size;
    // 记录 i 指向 cursor
    int i = cursor;
    if (i < size) {
        // 判断如果超过 elementData 大小,说明可能被修改了,抛出 ConcurrentModificationException 异常
        final Object[] es = elementData;
        if (i >= es.length)
            throw new ConcurrentModificationException();
        // 逐个处理
        for (; i < size && modCount == expectedModCount; i++)
            action.accept(elementAt(es, i));
        // update once at end to reduce heap write traffic
        // 更新 cursor 和 lastRet 的指向
        cursor = i;
        lastRet = i - 1;
        // 校验是否数组发生了变化
        checkForComodification();
    }
}
序列化
  • 序列化
@java.io.Serial
private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException {
    // 获得当前的数组修改次数
    int expectedModCount = modCount;

    // <1> 写入非静态属性、非 transient 属性
    s.defaultWriteObject();

    // <2> 写入 size ,主要为了与 clone 方法的兼容
    s.writeInt(size);

    // <3> 逐个写入 elementData 数组的元素
    for (int i = 0; i < size; i++) {
        s.writeObject(elementData[i]);
    }

    // 如果 other 修改次数发生改变,则抛出 ConcurrentModificationException 异常
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}
  • 反序列化
@java.io.Serial
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {

    // 读取非静态属性、非 transient 属性
    s.defaultReadObject();

    // 读取 size ,不过忽略不用
    s.readInt(); // ignored

    if (size > 0) {
        // like clone(), allocate array based upon size not capacity
        SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size); 
        // 创建 elements 数组
        Object[] elements = new Object[size];
 
        // 逐个读取
        for (int i = 0; i < size; i++) {
            elements[i] = s.readObject();
        }

        // 赋值给 elementData
        elementData = elements;
    } else if (size == 0) {
        // 如果 size 是 0 ,则直接使用空数组
        elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new java.io.InvalidObjectException("Invalid size: " + size);
    }
}
时间复杂度
  • 查询
    • ArrayList 随机访问时间复杂度是 O(1) ,查找指定元素的平均时间复杂度是 O(n) 。
  • 删除
    • ArrayList 移除指定位置的元素的最好时间复杂度是 O(1) ,最坏时间复杂度是 O(n) ,平均时间复杂度是 O(n) 。
      • 最好时间复杂度发生在末尾移除的情况。
    • ArrayList 移除指定元素的时间复杂度是 O(n) 。
      • 因为首先需要进行查询,然后在使用移除指定位置的元素,无论怎样,都需要 O(n) 的时间复杂度。
  • 添加
    • ArrayList 添加元素的最好时间复杂度是 O(1) ,最坏时间复杂度是 O(n) ,平均时间复杂度是 O(n) 。
      • 最好时间复杂度发生在末尾添加的情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值