这可能是最详细的ArrayList源码分析

本文内容基于 jdk1.8 环境

本文最先发布于我的个人博客,掘金为同步发布,如有需要,可以访问腿短快跑的个人博客获取更多内容

源码获取

jdk 源码在我们 jdk 的安装目录下即可找到:

  • jdk1.8

在 jdk1.8 及之前的版本中,jdk的源码均可在安装目录的根目录下找到 src.zip,此包即为 jdk 源码

  • jdk11

从 jdk11 开始,jdk的源码包放在 jdk 安装目录下的 lib 目录下,在 lib 目录下找到 src.zip 即为源码

实现接口

ArrayList 实现了以下接口:

  • List

实现了 List 接口,Java集合框架中常用接口

  • RandomAccess

用于实现随机访问功能,因为底层使用数组实现,可以通过下标实现随机访问

  • Cloneable

克隆接口,用于实现两个 List 之间的克隆

  • Serializable

序列化接口

核心属性

// 定义默认List大小
private static final int DEFAULT_CAPACITY = 10;

// 用于存储实际 ArrayList 中的数据
transient Object[] elementData;

// 用于存储当前 ArrayList 的大小
private int size;
复制代码

从上述代码中我们可以得出以下结论:

  • ArrayList 默认初始化大小为 10
  • ArrayList 实际大小使用 size 变量存储
  • ArrayList 底层数据使用 Object 数组存储

为什么数组使用transient关键字

transient关键字用于修饰类成员变量,不能用于修饰方法,用于标记对应的类成员变量不是类持久化状态的一部分。

jvm默认序列化和反序列创建字节流的过程中会忽略 transient 关键字修饰的类成员变量

ArrayList 中底层数组使用 transient 修饰是出于性能考虑,因为底层数据结构是数组,因此必然存在很多数组空间未使用的情况,直接序列化将导致序列化性能大大降低和资源的浪费

虽然因为底层数组使用 transient 修饰导致数组不能由jvm默认序列化,但是通过定义writeObject和readObject方法,自定义实现了列表元素的序列化与反序列化。此内容文章后续讲解

核心方法

构造方法

此处以指定大小为例,其他构造方法类似

用于初始化 ArrayList

// 默认空数组
private static final Object[] EMPTY_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);
        }
    }
复制代码
  1. 如果初始化大小 > 0 ,则创建一个对应大小的 Object 数组
  2. 如果初始化大小 == 0,则使用默认的静态空数组,此处是为了减少空间占用,避免多个空数组占用内存
  3. 如果初始化大小 < 0,则抛出异常

indexOf(Object o)

查找元素出现的第一个位置

    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }
复制代码
  1. 判断元素是否为空
  2. 遍历数组列表,找到值相同的索引返回,此处比较是否相同使用的 equals 方法
  3. 如果未找到对应的元素,返回 -1

lastIndexOf(Object o)

查找元素出现的最后一个位置

    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }
复制代码
  1. 判断元素是否为空
  2. 从数组末尾向前遍历,找到第一个值相同的索引返回,同上,使用 equals 方法
  3. 如果未找到对应的元素,返回 -1

clone()

克隆出一个新的 List

    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }
复制代码
  1. 底层是使用 System.arraycopy 实现的
  2. System.arraycopy 是native方法
  3. System.arraycopy 当数组是基础数据类型或者是 String 类型的时候是深拷贝
  4. System.arraycopy 当数组是非基础数据类型且非 String 类型的时候是浅拷贝
  5. System.arraycopy 是不安全的

get(int index)

获取指定位置的元素

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

        return elementData(index);
    }
复制代码
  1. 进行范围检查,如果出现数组越界,直接抛出异常
  2. 返回数组对应下标位置的数据

set(int index, E element)

设置指定位置的元素

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

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
复制代码
  1. 进行范围检查,如果出现数组越界,直接抛出异常
  2. 获取对应索引位置的值
  3. 将对应索引位置的值设置为新值
  4. 返回旧值

add(E e)

在 ArrayList 的末尾添加元素

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
复制代码
  1. 当前大小+1作为最小数组容量
  2. 检查容量是否需要扩容,如果需要扩容,则执行扩容操作
  3. 设置数组后面一个元素为对应的元素
  4. 返回结果

扩容操作

数组扩容操作

    /**
     * 检查容量,不满足则扩容
     */
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    /**
     * 计算应该扩容到的容量
     */
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
    /**
     * 判断是否需要扩容
     */
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    /**
     * 扩容操作
     */
    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);
    }
复制代码
  1. 计算当前应该扩容到的容量 a. 数组是否是默认的空数组,是则返回当前最小容量和默认容量(10)的最大值 b. 否则,返回当前最小容量
  2. 判断是否需要扩容 a. 先对 modCount 增加,modCount 用于标识数组是否被修改过,用于提供快速失败的实现 b. 判断应该扩容到的容量是否超过了当前底层数组的实际大小,如果超过了才需要扩容
  3. 如果需要扩容则执行扩容 a. 计算出新的数组大小 = 旧的数组的大小 + 旧的数组的大小 / 2,即,扩容后的大小为原数组大小的 1.5 倍,采用 >> 1 的写法是为了提升性能 b. 如果新的数组的大小 < 应该扩容的数组的大小,则新的数组的大小设置为应该扩容的数组的大小 c. 如果新的数组的大小超出了最大数组的大小(Integer.MAX_VALUE - 8 = 2147483639),则新数组的大小设置为 Integer.MAX_VALUE,否则为数组的最大大小(Integer.MAX_VALUE - 8 = 2147483639) d. 如果扩容的数组的大小出现了数据溢出,则会抛出 OutOfMemoryError 内存溢出异常 e. 创建新的数组,将旧数组数据复制到新的数组中

add(int index, E element)

在指定索引位置插入元素

    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
复制代码
  1. 范围检查,如果出现数组越界,抛出异常
  2. 当前大小+1作为数组最小容量
  3. 检查容量是否需要扩容,如果需要扩容,则执行扩容操作
  4. 将指定位置的数组内容向后移动一位
  5. 在指定位置设置数组的元素值
  6. 数组size+1

remove(int index)

移除指定位置的元素

    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;
    }
复制代码
  1. 进行范围检查,如果出现数组越界,直接抛出异常
  2. 获取对应索引位置的数据
  3. 计算出需要移动的位置的索引,如果索引 > 0,则将后面的数组的内容向前移动一位
  4. 将数组的最后一个元素设置为null
  5. 返回旧数据

remove(Object o)

移除指定元素,仅会移除第一次出现的元素

    public boolean remove(Object o) {
        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;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }
复制代码
  1. 判断要移除的元素是否为null
  2. 从开始位置遍历数组元素,判断元素是否相等,如果相等,则执行快速移除(同 remove(int index) 方法的实现),移除后返回结果

clear()

清空 ArrayList

    public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }
复制代码
  1. 修改数量+1,用于提供快速失败机制
  2. 从开始位置遍历数组,将数组中的元素全部设置为null
  3. 设置 size 大小为 0

addAll(Collection<? extends E> c)

添加集合元素至 ArrayList 结尾

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }
复制代码
  1. 将集合转换为数组
  2. 当前大小+新数组的大小作为最小容量
  3. 根据最小容量判断是否需要扩容,如果需要扩容,执行扩容操作
  4. 将新数组复制到数组的末尾
  5. size大小更新为当前新的大小
  6. 返回新增结果

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

在指定位置添加集合元素

    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }
复制代码
  1. 进行范围检查,如果出现数组越界,直接抛出异常
  2. 将集合转换为数组
  3. 当前大小 + 新数组的大小作为最小容量
  4. 根据最小容量判断是否需要扩容,如果需要扩容,执行扩容操作
  5. 将当前数组从index开始位置的元素全部向后移动新数组大小的位置
  6. 将新数组的元素从index位置开始复制
  7. size大小更新为当前新的大小
  8. 返回新增结果

removeRange(int fromIndex, int toIndex)

移除指定区间内的元素

    protected void removeRange(int fromIndex, int toIndex) {
        modCount++;
        int numMoved = size - toIndex;
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                         numMoved);

        // clear to let GC do its work
        int newSize = size - (toIndex-fromIndex);
        for (int i = newSize; i < size; i++) {
            elementData[i] = null;
        }
        size = newSize;
    }
复制代码
  1. 修改数量+1,用于提供快速失败机制
  2. size-toIndex是需要移动的元素的个数
  3. 将需要移动的元素移动至 fromIndex 位置
  4. size - (toIndex-fromIndex) 是现在新的数组的实际大小
  5. 从新的大小位置开始,将后面的元素全部设置为null
  6. size设置为新的大小

removeAll(Collection<?> c)

移除包含集合中的所有元素

    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }
    
    /**
     * 批量移除元素
     */
    private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }
复制代码

注意 complement 参数值为 false

  1. 检查集合元素是否为空,为空则抛出异常
  2. 遍历数组,如果集合中不包含当前元素(通过 complement 值来判断),则将当前元素依次从 r 位置排列
  3. 当 r != size 的时候说明 c.contains 可能出现了异常,做了一个兼容操作,此处不详细讲述
  4. w表示移动后的集合的元素的长度,如果 w != size 则证明存在元素被修改,将 w 位置后面的元素全部设置为 null, 并将 size 设置为 w
  5. 返回修改结果

retainAll(Collection<?> c)

ArrayList 和 集合取交集

    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, true);
    }
复制代码

注意 complement 参数值为 true

步骤和上面 removeAll 方法实现逻辑一样,只是 complement 参数值不一样,通过 complement 参数来控制是保留包含的元素还是不包含的元素

writeObject & readObject

序列化使用,上面内容我们讲到为了保证性能和避免资源浪费,底层数组使用了 transient 关键字修复,导致无法使用 jvm 的序列化,通过 writeObject 和 readObject 方法实现了 ArrayList 的序列化和反序列化

    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
    
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            int capacity = calculateCapacity(elementData, size);
            SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }
复制代码

总结

从上述源码中我们可以得出以下结论:

  • ArrayList也是List 的一种
  • ArrayList底层使用 Object 数组实现
    • ArrayList比较适合读多写少的情况(写需要挪动元素)
  • ArrayList初始容量大小为10
  • ArrayList每次扩容大小为原容量的 1.5 倍

猜你喜欢

转载自juejin.im/post/7102190099953090597