ArrayList 实现原理及源码解析(jdk8 底层⽤的是数组)

本文会主要从集合扩容、添加元素、删除元素三个方面来进行源码解读

在开始解读之前,我们先来看一下ArrayList添加一个元素的流程
在这里插入图片描述
基本在图中已经比较全面的说明了add一个元素的全流程

源码解读

我们先来看ArrayList中几个关键的字段

/**
     * ArrayList默认的容量为10个Object
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 用于空实例的共享空数组实例
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 存储ArrayList的数组缓冲区,添加第一个元素将扩容为DEFAULT_CAPACITY
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * ArrayList中包含的元素数量
     *
     * @serial
     */
    private int size;

这几个字段待会分析都会用到,需要都理解,接下来看下无参构造方法

    /**
     * 构造一个初始容量为10的空列表
     */
    // eg1:初始化ArrayList,则elementData = {}
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

从这里开始,我们分析ArrayList添加一个方法的具体过程

    /**
     * 新增元素到数组末尾
     */
    // eg1:第一次新增元素e="a1"
    public boolean add(E e) {
        // 确定是否需要扩容,如果需要则进行扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // eg1:size=0,elementData[0]="a1",然后size自增为1
        elementData[size++] = e;
        return true;
    }

先看下ensureCapacityInternal方法,入参为size+1

    // eg1:
    private void ensureCapacityInternal(int minCapacity) {
        // eg1:calculateCapacity方法返回10,ensureExplicitCapacity方法不需要扩容(minCapacity=size+1)
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

这里调用了计算容量的方法calculateCapacityminCapacity=size+1,看看calculateCapacity方法

    /**
     * 计算ArrayList容量
     * <p>
     * 如果elementData数组中没有一存储的元素,则返回默认值10(DEFAULT_CAPACITY)
     * 否则返回minCapacity
     *
     * @param elementData 底层存储的ArrayList元素的数组
     * @param minCapacity ArrayList中元素的个数
     * @return
     */
    // // eg1:第一次新增元素,minCapacity=1 elementData={}
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        // eg1:满足条件,返回10(DEFAULT_CAPACITY)
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

看eg1,这里返回DEFAULT_CAPACITY,也就是10,然后再看下ensureExplicitCapacity方法

    /**
     * 确保明确的ArrayList的容量
     *
     * @param minCapacity ArrayList所需的最小容量
     */
    // eg1:minCapacity=10
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // 如果所需要的容量大于elementData数组的容量,则进行扩容操作
        // eg1:10-0>0,满足扩容需求
        if (minCapacity - elementData.length > 0)
            // 扩容
            grow(minCapacity);
    }

这里是满足if条件,所以需要调用grow方法

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     * <p>
     * 扩容操作
     *
     * @param minCapacity 所需的最小扩容容量
     */
    // eg1:第一次扩容,minCapacity=10,满足第一个if条件,则将elementData数组扩容为10长度
    private void grow(int minCapacity) {
        // 原有数组elementData的长度
        int oldCapacity = elementData.length;
        // 新增oldCapacity的一半整数长度作为newCapacity的额外长度(右移一位代表除2)
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 扩容后的长度依然无法满足最小容量minCapacity,则新的扩容直接为minCapacity
        if (newCapacity - minCapacity < 0)
            // newCapacity=10
            newCapacity = minCapacity;
        // 新的扩容长度newCapacity超出了最大的数组长度MAX_ARRAY_SIZE
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 将旧数组中的元素赋值到新的扩容后的数组中
        // eg1:newCapacity=10, 扩容elementData的length=10
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

grow方法也是主要的扩容逻辑,grow完成后返回到add方法,将扩容后的elementData(这时候是10),将elementData[0]="a1"

    /**
     * 新增元素到数组末尾
     */
    // eg1:第一次新增元素e="a1"
    public boolean add(E e) {
        // 确定是否需要扩容,如果需要则进行扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // eg1:size=0,elementData[0]="a1",然后size自增为1
        elementData[size++] = e;
        return true;
    }

至此就完成了一次元素添加操作

接下来我们看下remove方法,这个方法比较简单,代码如下:

    /**
     * 删除元素
     */
    // eg1:elementData中保存了{"a1","a2","a3","a4"},删除第一个元素,index=0
    public E remove(int index) {
        // 校验传入的参数index是否超出了数组的最大下标,如果超出则抛出:ArrayIndexOutOfBoundsException
        rangeCheck(index);

        modCount++;
        // 取出此下标元素
        // eg1:oldValue="a1"
        E oldValue = elementData(index);

        // 需要一定的元素数量
        // eg1:numMoved=4-0-1=3
        int numMoved = size - index - 1;
        if (numMoved > 0)
            // 从需要删除的index后一位开始到末尾的这部分数据整体都向前移动一个元素(原有index的元素就被挤到了最后)
            System.arraycopy(elementData, index + 1, elementData, index, numMoved);
        // 将最后一位元素置为null,通知jvm将其垃圾回收
        elementData[--size] = null;

        // 返回已被删除的元素
        return oldValue;
    }

源码中多次出现的modCount作用是什么?

在ArrayList中,所有修改集合元素的的操作都有modCount,这是在它的父类AbstractList,顾名思义,就是修改次数,接下来我们就聊聊modCount的作用

    /**
     * The number of times this list has been <i>structurally modified</i>.
     * Structural modifications are those that change the size of the
     * list, or otherwise perturb it in such a fashion that iterations in
     * progress may yield incorrect results.
     *
     * <p>This field is used by the iterator and list iterator implementation
     * returned by the {@code iterator} and {@code listIterator} methods.
     * If the value of this field changes unexpectedly, the iterator (or list
     * iterator) will throw a {@code ConcurrentModificationException} in
     * response to the {@code next}, {@code remove}, {@code previous},
     * {@code set} or {@code add} operations.  This provides
     * <i>fail-fast</i> behavior, rather than non-deterministic behavior in
     * the face of concurrent modification during iteration.
     *
     * <p><b>Use of this field by subclasses is optional.</b> If a subclass
     * wishes to provide fail-fast iterators (and list iterators), then it
     * merely has to increment this field in its {@code add(int, E)} and
     * {@code remove(int)} methods (and any other methods that it overrides
     * that result in structural modifications to the list).  A single call to
     * {@code add(int, E)} or {@code remove(int)} must add no more than
     * one to this field, or the iterators (and list iterators) will throw
     * bogus {@code ConcurrentModificationExceptions}.  If an implementation
     * does not wish to provide fail-fast iterators, this field may be
     * ignored.
     */
    protected transient int modCount = 0;

注释大致意思就是:
modCount 记录集合被的次数。结构修改是那些改变列表大小的修改,或者以这样一种方式扰乱列表,使得进行中的迭代可能产生不正确的结果。

如果该字段的值发生意外变化,迭代器(或列表迭代器)将抛出ConcurrentModificationException来响应next、remove、previous、set或add操作。这提供了fail-fast行为,而不是在迭代期间面对并发修改时的非确定性行为。

通过子类使用该字段是可选的。如果子类希望提供快速失败迭代器(和列表迭代器),那么它只需在add(int, E)、remove(int)方法(以及它覆盖的导致列表结构修改的任何其他方法)中增加该字段。

单个调用add(int, E)或remove(int)必须执行modCount++且不超过一个,否则迭代器(和列表迭代器)将抛出ConcurrentModificationExceptions。如果实现不希望提供快速失败的迭代器,则可以忽略此字段。

主要作用

  1. 识别多线程并发修改并报错,有modCount一般都是线程不安全
  2. 对集合迭代过程中验证是否被修改,如果被修改则抛出:ConcurrentModificationException

源码解读

modCount主要用于避免迭代的时候集合被其它线程修改,这里我们看下AbstractList中的私有类Itr

    private class Itr implements Iterator<E> {
        /**
         * 调用next返回的索引
         */
        int cursor = 0;

        /**
         * 上一个元素,如果通过调用remove删除该元素,则重置为-1。
         */
        int lastRet = -1;

        /**
         * 迭代器的期望值为modCount,如果迭代过程中检查两个值不同,则认为被并发修改了
         */
        int expectedModCount = modCount;

        /**
         * 是否有下一个元素
         */
        public boolean hasNext() {
            // cursor不等于数组长度则表示还有
            return cursor != size();
        }

        /**
         * 获取下一个元素
         */
        public E next() {
            //
            checkForComodification();
            try {
                int i = cursor;
                E next = get(i);
                // 记录最后一个获取的元素
                lastRet = i;
                // 位置往前移一个
                cursor = i + 1;
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        /**
         * 移除元素
         */
        public void remove() {
            // 如果没有执行next,直接remove,则抛出IllegalStateException
            if (lastRet < 0)
                throw new IllegalStateException();
            // 检查是否被修改
            checkForComodification();

            try {
                // 移除next时记录的cursor的元素
                AbstractList.this.remove(lastRet);
                if (lastRet < cursor)
                    cursor--;
                // 重置最后一个next的元素位置
                lastRet = -1;
                // 重置期望的modeCount
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

        /**
         * 【重要】检查迭代过程中集合元素是否有被修改,被修改则抛出ConcurrentModificationException
         */
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

在代码中,如果没有执行next,直接调用remove,那么下次执行remove就会因lastRet=-1而抛出IllegalStateException
对非线程安全的集合操作推荐使用迭代器,迭代过程中会有进行多线程修改判断,否则非常容易发生数组越界

如果喜欢我的文章记得一定要一键三连哟(别下次一定!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我思知我在

原创不易,多多一键三连

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值