浅析 ArrayList 源码(JDK 1.8)

浅析 ArrayList 源码(JDK 1.8)

1. ArrayList 的继承类图

在这里插入图片描述

  • Serializable 接口:标记接口,实现此接口被标记为可序列化的。
  • RandomAccess 接口:标记接口,实现此接口被标记为支持快速随机访问。
  • Cloneable 接口:标记接口,实现此接口被标记为可克隆的。
  • List 接口:常规接口,实现此接口可以使用对有序集合的增删改查等基础操作。

2. ArrayList 的成员变量

    /**
     * 默认初始容量
     */
    private static final int DEFAULT_CAPACITY = 10;

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

    /**
     * 用于默认大小的空实例的共享空数组实例。我们将其与EMPTY_ELEMENTDATA区分开来,以了解添加第一个元素时要膨胀多少
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 存储数组列表元素的数组缓冲区
     * 数组列表的容量是此数组缓冲区的长度
     * 添加第一个元素时,任何符合 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 条件的空 ArrayList 都需要将数组列
     * 表的初始大小扩展到 DEFAULT_CAPACITY
     */
    transient Object[] elementData; // 非私有,以简化嵌套类访问

    /**
     * 数组列表的大小(它包含的元素数)
     */
    private int size;

    /**
     * 数组的列表的最大容量
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

注意:elementData 是被 transient 关键字修饰,不会被序列化为字节流。

3. ArrayList 的自定义序列化

    // 序列化
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // 首先获取modCount,防止并发操作
        int expectedModCount = modCount;
        // 调用ObjectOutputStream的方法,将当前类没有被修饰transient和不是static的字段写入流中
        // 因为elementData被transient修饰了,所以这里不会写入
        s.defaultWriteObject();

        // 写出elementData数组大小size
        s.writeInt(size);

        // 根据size值大小遍历elementData数组,即仅写出非null的元素
        // 因为一般数组扩容后,数组会自动补充elementData.length() - size数量的null值
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        // 判断当前modCount值与开始取得值是否一致,若不等于,说明在此期间,已有其余线程对此ArrayList进行操作,抛出异常
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

    // 反序列化
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // 初始化elementData
        elementData = EMPTY_ELEMENTDATA;

        // 调用ObjectInputStream的方法,从流中读取非static字段以及非transient字段
        s.defaultReadObject();

        // 读取数组的容量
        s.readInt(); // ignored

        // 判断数组是否存在元素
        if (size > 0) {
            // 计算数组容量,因为elementData = EMPTY_ELEMENTDATA,所以永远有capacity=size
            int capacity = calculateCapacity(elementData, size);
            SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
            // 根据size大小,对elementData进行扩容
            ensureCapacityInternal(size);

            Object[] a = elementData;
            //将数据依次读取
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }

4. ArrayList 的构造函数

4.1 ArrayList(int initialCapacity) 构造函数

    /**
     * 构造具有指定初始容量的空列表
     *
     * @param  initialCapacity – 列表的初始容量
     * @throws IllegalArgumentException – 条件为传入列表的初始容量为负数
     */
    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);
        }
    }

此构造函数通过对传入 initialCapacity 进行校验,得到不同elementData解决方案

  • initialCapacity > 0,elementData 初始化为容量为 initialCapacityObject 的数组
  • initialCapacity = 0,将 elementData 赋值为空数组 EMPTY_ELEMENTDATA
  • initialCapacity < 0,抛出非法容量 Illegal Capacity 异常

4.2 ArrayList() 无参构造函数

    /**
     * 构造初始容量为 10 的空列表
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
  • elementData 赋值为空数组 DEFAULTCAPACITY_EMPTY_ELEMENTDATA

4.3 ArrayList(Collection<? extends E> c) 构造函数

    /**
     * 按照集合的迭代器返回的顺序构造包含指定集合的元素的列表
     *
     * @param c – 其元素要放入此列表的集合
     * @throws NullPointerException – 条件为传入的集合为空
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray 可能(错误地)不返回 Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 替换为空数组
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
  • 将传入集合 c 转换为 Object 数组,并赋值给 elementData

  • 判断 elementData 长度是否为0。

    • 若长度为0,则将 elementData 替换为空数组 EMPTY_ELEMENTDATA

    • 若长度不为0,进行 elementData 类型的判断,因为 toArray 可能(错误地)不返回 Object[],若判断为否,则通过 Arrays.copyOfelementData 转换为 Object[] 类型。

5. ArrayList 的扩容机制

  • ArrayList 中,如果需要添加元素,会使用 ensureCapacityInternal 方法来确保当前容量足够,如果不够则需要进行扩容操作。
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

进行添加元素操作时,minCapacity 传值为 size + 添加元素数量

  • 首先使用 calculateCapacity 计算 elementData 需要的最小容量。
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        // 判断elementData是否被初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
  • 再调用 ensureExplicitCapacity 来确保 elementData 当前的数组长度是否满足计算得到的最小容量。
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
  • 不满足则调用grow 方法进行扩容
    /**
     * 增加容量以确保它至少可以容纳最小容量参数指定的元素数.
     *
     * @param minCapacity 所需的最小容量
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        
        // >> 的数学意义相当于除2,此处右移1位,表示oldCapacity除以2的1次方
        // 因此ArrayList的扩容是默认将原数组扩容1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        
        // 当插入大量元素时,即addAll方法,传入的minCapacity远大于1.5倍扩容的newCapacity,则直接赋值为minCapacity
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        
        // 判断扩容后的容量是否大于MAX_ARRAY_SIZE 
        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);
    }

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

6. 检查传入索引是否越界通用方法

6.1 rangeCheck(int index)

ArrayList 中,get(int index)set(int index, E element) 以及 remove(int index) 进行了调用。

    /**
     * 检查给定索引是否在范围内。如果不是,则引发相应的运行时异常
     * 此方法不检查索引是否为负数:它总是在数组访问之前使用,如果索引为负数,则会抛出ArrayIndexOutOfBoundsException
     */
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

6.2 rangeCheckForAdd(int index)

ArrayList 中,add(int index, E element) 以及 addAll(int index, Collection<? extends E> c) 进行了调用。

    /**
     * add and addAll 使用的 rangeCheck 版本
     */
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

7. ArrayList 的增加方法

7.1 add(E e)

    /**
     * 将指定的元素追加到此列表的末尾
     *
     * @param e 要附加到此列表的元素
     * @return true
     */
    public boolean add(E e) {
        // 判断是否需要扩容,是则进行扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

7.2 add(int index, E element)

    /**
     * 在此列表中的指定位置插入指定的元素。将当前位于该位置的元素(如果有)和任何后续元素向右移动(将一个元素添加到其索引中)
     *
     * @param index 要插入指定元素的索引
     * @param element 要插入的元素
     * @throws IndexOutOfBoundsException
     */
    public void add(int index, E element) {
        // 检查传入索引是否合法
        rangeCheckForAdd(index);

        // 判断是否需要扩容,是则进行扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 此数组拷贝方法为native本地方法
        // 旨在将elementData中从index索引开始到size-index个元素的索引对应元素向后移动index+1-index位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        // 将元素插入到当前下标的位置 
        elementData[index] = element;
        size++;
    }

7.3 addAll(Collection<? extends E> c)

    /**
     * 将指定集合中的所有元素追加到此列表的末尾,顺序与指定集合的迭代器返回这些元素的顺序相同。
     * 如果在操作过程中修改了指定的集合,则未定义此操作的行为。
     * (这意味着如果指定的集合是此列表,并且此列表为非空,则此调用的行为是未定义的)
     *
     * @param c 包含要添加到此列表中的元素的集合
     * @return 如果此列表因调用而更改,则为 true
     * @throws NullPointerException 如果指定的集合为空
     */
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        // 判断是否需要扩容,是则进行扩容
        ensureCapacityInternal(size + numNew);  // Increments modCount
        // 从源数组a的0索引起始向目标数组elementData的size索引起始复制numNew个元素
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

7.4 addAll(int index, Collection<? extends E> c)

    /**
     * 从指定位置开始,将指定集合中的所有元素插入到此列表中
     * 将当前位于该位置的元素(如果有)和任何后续元素向右移动(增加其索引)
     * 新元素将按照指定集合的迭代器返回的顺序显示在列表中
     *
     * @param index 插入指定集合中的第一个元素的索引
     * @param c 包含要添加到此列表中的元素的集合
     * @return 如果此列表因调用而更改,则为 true
     * @throws IndexOutOfBoundsException 如果索引超出范围(索引 < 0 || 索引 > size())
     * @throws NullPointerException 如果指定的集合为空
     */
    public boolean addAll(int index, Collection<? extends E> c) {
        // 检查传入索引是否合法
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        // 判断是否需要扩容,是则进行扩容
        ensureCapacityInternal(size + numNew);  // Increments modCount

        // 当 0 <= index < size 时
        // 从源数组elementData的index索引起始向目标数组elementData的index + numNew索引起始复制numMoved个元素 
        // 即将elementData的index索引到size-index个元素的索引对应元素往后移index+numNew-index位
        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        // 从源数组a的0索引起始向目标数组elementData的index索引起始复制numNew个元素
        // 当 0 <= index < size 时,此copy是将a插入elementData的index到index+numNew
        // 当 index = size 时,此copy是将a放入elementData的size后面,即末尾
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

8. ArrayList 的删除方法

8.1 remove(int index)

    /**
     * 删除此列表中指定位置的元素。将任何后续元素向左移动(从其索引中减去一个)
     *
     * @param index 要删除的元素的索引
     * @return 从列表中删除的元素
     * @throws IndexOutOfBoundsException 如果索引超出范围(索引 < 0 || 索引 >= size())
     */
    public E remove(int index) {
        // 检查传入索引是否合法
        rangeCheck(index);

        modCount++;
        // 获取elementData中index下标对应元素,即需要被remove的元素
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        // 当index = size - 1时,即删除数组最后一个元素,此时elementData不需要进行copy操作
        if (numMoved > 0)
        	// 当 0 <= index < size - 1时,
            // 从源数组elementData的index+1索引起始向目标数组elementData的index索引起始复制numMoved个元素,
            // 即将elemetData中从索引index+1到size-1的元素位移index-(index+1)=-1位,也就是左移1位
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // 将数组最后一位的引用置null,对象不再有指向,被GC回收
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

8.2 remove(Object o)

    /**
     * 从此列表中移除首次出现的指定元素(如果存在)。 
     * 如果列表不包含该元素,则它不变。 
     * 更正式地说,删除具有最小索引 i 的元素,使得(o==null ? get(i)==null : o.equals(get(i)))(如果存在这样的元素)。 
     * 如果此列表包含指定元素,则返回 true(或者等效地,如果此列表因调用而更改)。
     *
     * @param o 要从此列表中删除的元素(如果存在)
     * @return 如果此列表包含指定的元素,则为 true
     */
    public boolean remove(Object o) {
        // 数组顺序排序,0~size,删除首个匹配的元素
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    /*
     * 跳过边界检查并且不返回被删除值的私有删除方法。
     */
    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        // 当index = size - 1时,即删除数组最后一个元素,此时elementData不需要进行copy操作
        if (numMoved > 0)
            // 当 0 <= index < size - 1时,
            // 将elemetData中从索引index+1到size-1的元素位移index-(index+1)=-1位,也就是左移1位
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // 将数组最后一位的引用置null,对象不再有指向,被GC回收
        elementData[--size] = null; // clear to let GC do its work
    }

8.3 removeAll(Collection<?> c)

    /**
     * 从此列表中删除指定集合中包含的所有元素
     *
     * @param c 包含要从此列表中删除的元素的集合
     * @return true 如果此列表因调用而更改
     * @throws ClassCastException 如果此列表元素的类与指定的集合不兼容(可选)
     *  	   NullPointerException 如果此列表包含 null 元素,并且指定的集合不允许 null 元素(可选),
     *  	   或者指定的集合为 null
     */
    public boolean removeAll(Collection<?> c) {
        // 对c集合进行判空
        Objects.requireNonNull(c);
        // 批处理移除,通过此方法的第二参数进行控制删除规则
        return batchRemove(c, false);
    }

8.4 retainAll(Collection<?> c)

    /**
     * 仅保留此列表中包含在指定集合中的元素
     * 换句话说,从此列表中删除指定集合中未包含的所有元素
     *
     * @param c 包含要保留在此列表中的元素的集合
     * @return true 如果此列表因调用而更改
     * @throws ClassCastException 如果此列表元素的类与指定的集合不兼容(可选)
     *  	   NullPointerException 如果此列表包含 null 元素,并且指定的集合不允许 null 元素(可选),
     *  	   或者指定的集合为 null
     */
    public boolean retainAll(Collection<?> c) {
        // 对c集合进行判空
        Objects.requireNonNull(c);
        // 批处理移除,通过此方法的第二参数进行控制删除规则
        return batchRemove(c, true);
    }

8.5 batchRemove(Collection<?> c, boolean complement)

    /**
     * 批量删除指定集合中的元素
     *
     * @param c 包含要保留在此列表中或删除的元素的集合
     * @param complement 是否删除与集合中的元素不匹配的元素,true,则删除c不匹配的元素,false,则删除c匹配的元素
     */
	private boolean batchRemove(Collection<?> c, boolean complement) {
        // 获取元素数组
        final Object[] elementData = this.elementData;
        // r为读指针,w为写指针,modified为是否修改了列表
        int r = 0, w = 0;
        boolean modified = false;
        try {
            // 遍历元素数组
            for (; r < size; r++)
                // 如果集合包含该元素且complement参数为true,或者集合不包含该元素且complement参数为false
                if (c.contains(elementData[r]) == complement)
                    // 将该元素从新数组中删除,就是将保留的元素从索引0开始覆盖
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            // 一般情况下,r是等于size的,但删除元素时c.contains有可能发生异常
            if (r != size) {
                // 根据此时的r和w来判断,保留elementData遍历到r索引及后面的元素
                // 还有保留符合c.contains(elementData[r]) == complement这个条件的元素
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                // 更新w值,以方便后续索引对应元素置null
                w += size - r;
            }
            if (w != size) {
                // clear to let GC do its work
                // 将数组w索引后的元素引用置null,对象不再有指向,被GC回收
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                // 更新数组大小size
                size = w;
                modified = true;
            }
        }
        return modified;
    }

8.6 removeIf(Predicate<? super E> filter)

    /**
     * 删除列表中所有满足过滤条件的元素
     *
     * @param filter 过滤条件
     * @return 如果列表中的元素发生了变化,返回true
     * @throws NullPointerException 如果filter为null
     */
    @Override
    public boolean removeIf(Predicate<? super E> filter) {
        // 确保传入的过滤器不为null
        Objects.requireNonNull(filter);

        // figure out which elements are to be removed
        // any exception thrown from the filter predicate at this stage
        // will leave the collection unmodified
        // 记录需要删除的元素个数及其下标
        int removeCount = 0;
        final BitSet removeSet = new BitSet(size);
        // 记录当前modCount的值,用于在遍历过程中检测集合是否被修改
        final int expectedModCount = modCount;
        // 记录当前集合中元素的个数
        final int size = this.size;
        // 遍历集合中的元素,找到需要删除的元素
        for (int i = 0; modCount == expectedModCount && i < size; i++) {
            @SuppressWarnings("unchecked")
            final E element = (E) elementData[i];
            // 如果当前元素满足过滤条件
            if (filter.test(element)) {
                // 标记该元素需要被删除,并统计需要删除的元素数量
                removeSet.set(i);
                removeCount++;
            }
        }

        // 检查集合在遍历过程中是否被修改
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }

        // shift surviving elements left over the spaces left by removed elements
        final boolean anyToRemove = removeCount > 0; // 是否有元素需要被删除
        if (anyToRemove) {
            final int newSize = size - removeCount; // 新的列表大小
            for (int i = 0, j = 0; (i < size) && (j < newSize); i++, j++) {
                // 找到下一个不需要删除的元素,并将其移动到左侧空出的位置
                /**
                 *  假设size为5,且i=1、3时的元素需要删除
                 *  则bitSet中元素可以简单的理解为:
                 *      索引0-false
                 *      索引1-true
                 *      索引2-false
                 *      索引3-true
                 *      索引4-false
                 *      索引5-true
                 *  nextClearBit(int fromIndex)可以简单理解为获取当前fromIndex及其后面首个为false的索引值
                 *  length()可以简单理解为最后一个true的索引值+1,因此这里的length为4
                 *  当fromIndex=0,得0
                 *  当fromIndex=1,得2
                 *  当fromIndex=2,得2
                 *  当fromIndex=3,得4
                 */
                i = removeSet.nextClearBit(i);
                elementData[j] = elementData[i];
            }
            // 将右侧被删除的元素置为null,以便GC清理
            for (int k = newSize; k < size; k++) {
                elementData[k] = null;  // Let gc do its work
            }
            // 更新列表大小
            this.size = newSize;

            // 检查modCount值是否发生变化
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            // 更新modCount
            modCount++;
        }

        // 返回列表中的元素是否发生了变化
        return anyToRemove;
    }

9. ArrayList 的修改方法

9.1 set(int index, E element)

    /**
     * 将此列表中指定位置的元素替换为指定的元素
     *
     * @param index 要替换的元素的索引
     * @param element 要存储在指定位置的元素
     * @return 先前位于指定位置的元素
     * @throws IndexOutOfBoundsException 如果索引超出范围(索引 < 0 || 索引 >= size())
     */
    public E set(int index, E element) {
        // 判断index是否合法
        rangeCheck(index);

        // 获取源数组中的元素
        E oldValue = elementData(index);
        // 替换元素
        elementData[index] = element;
        return oldValue;
    }

10. ArrayList 的查询方法

10.1 get(int index)

    /**
     * 返回此列表中指定位置的元素
     *
     * @param  index 要返回的元素的索引
     * @return 此列表中指定位置的元素
     * @throws IndexOutOfBoundsException 如果索引超出范围(索引 < 0 || 索引 >= size())
     */
    public E get(int index) {
        // 判断index是否合法
        rangeCheck(index);

        return elementData(index);
    }
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值