ArrayList 源码分析

转载地址:

本文基于版本为 1.8.0_281 的 JDK 对 ArrayList 的源码进行分析
在这里插入图片描述
不知道比较新的 JDK 内 ArrayList 内实现咋样的,应该大差不差吧,研究好主流的 JDK 1.8 一通百通。

全局概览

先看看 ArrayList 吧:public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable,继承了 AbstractList 类 并实现了 ListRandomAccessCloneablejava.io.Serializable 接口,有了 List 的方法、支持了「快速随机访问」、能被克隆、支持了序列化。

为了对 ArrayList 有个全局把控,咱们再利用 IDEA 的强大功能看看 ArrayList 都有哪些属性以及方法,如下图(其实下图中单单左侧也就够用了):
在这里插入图片描述
下面将对「成员变量」、「构造方法」、以及一些「常用方法」进行一些源码分析。

成员变量

如上图所示,没几个 成员变量,都给大家在下面的代码段中做了介绍:

	/**
	* 序列化 ID
	*/
	private static final long serialVersionUID = 8683452581122892189L;
<span class="token comment">/**
* 默认的初始化容量
*/</span>
<span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> DEFAULT_CAPACITY <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span>

<span class="token comment">/**
* 空数组:供其他需要用到空数组的地方调用
*/</span>
<span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> Object<span class="token punctuation">[</span><span class="token punctuation">]</span> EMPTY_ELEMENTDATA <span class="token operator">=</span> <span class="token punctuation">{<!-- --></span><span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token comment">/**
* 默认构造函数里的空数组:用来判断 ArrayList 第一次添加数据的时候要扩容多少
*/</span>
<span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> Object<span class="token punctuation">[</span><span class="token punctuation">]</span> DEFAULTCAPACITY_EMPTY_ELEMENTDATA <span class="token operator">=</span> <span class="token punctuation">{<!-- --></span><span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token comment">/**
* 真正存储数据的对象数组
*/</span>
<span class="token keyword">transient</span> Object<span class="token punctuation">[</span><span class="token punctuation">]</span> elementData<span class="token punctuation">;</span> <span class="token comment">// non-private to simplify nested class access</span>

<span class="token comment">/**
* ArrayList 中包含的元素个数
*/</span>
<span class="token keyword">private</span> <span class="token keyword">int</span> size<span class="token punctuation">;</span>

<span class="token comment">/**
* 要分配的数组的最大大小,为 [(2^31)-1]-8
*/</span>
<span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> MAX_ARRAY_SIZE <span class="token operator">=</span> Integer<span class="token punctuation">.</span>MAX_VALUE <span class="token operator">-</span> <span class="token number">8</span><span class="token punctuation">;</span>

构造方法

有三个 构造方法,咱们一个一个来介绍。

第一个,无参构造方法:

    /**
     * 构造一个空数组,添加第一个元素时容量变为 10(后面会介绍)
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

第二个,有参构造方法:

    /**
     * 构造一个具有指定初始容量大小的数组
     *
     * @param  initialCapacity  指定的初始容量大小
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
        	// 传入参数大于 0,则创建打下为 initialCapacity 的数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
        	// 传入参数为 0,创建空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
        	// 传入复数,抛出异常
            throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
        }
    }

第三个,还是有参构造方法:

    /**
     * 构造一个包含特定集合内的元素的数组,元素按照集合迭代器返回的顺序排列。
     *
     * @param c:元素被放入的集合
     */
    public ArrayList(Collection<? extends E> c) {
    	// 将集合转为数组 a
        Object[] a = c.toArray();
        if ((size = a.length) != 0) {
        	// 数组 a 长度不为 0
            if (c.getClass() == ArrayList.class) {
            	// a 是 ArrayList 类型,直接将 a 数组赋值给 ArrayList 中的数组
                elementData = a;
            } else {
            	// a 不是 ArrayList 类型,利用 Arrays.copyof() 方法将不是 ArrayList 类型的 a 数组内的内容赋值给新的 Object 类型的数组
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // 用空数组代替
            elementData = EMPTY_ELEMENTDATA;
        }
    }

常用方法

并不是所有的方法都写了,只写了常用的方法,常用的方法也没全写「比如 remove() 、add() 」等方法有几个重载,只写了最基础的。

get()

    /**
     * 返回指定位置的元素
     *
     * @param index:要返回的元素索引(下标)
     * @return E:返回指定位置的元素
     */
    public E get(int index) {
    	// 检查给定的索引(下标)值是否在范围内
        rangeCheck(index);
		// 将指定位置的元素返回
        return elementData(index);
    }

remove()

    /**
     * 删除此列表中指定位置的元素,并将其右侧的所有元素向左移动
     *
     * @param index:要移除元素的对应索引(下标)
     * @return E:从列表中删除的元素
     */
    public E remove(int index) {
    	// 检查给定的索引(下标)值是否在范围内
        rangeCheck(index);
		// 调整结构,和 fast-fail 有关
        modCount++;
        // 要被删除的元素
        E oldValue = elementData(index);
		// 需要移动的元素个数
        int numMoved = size - index - 1;
        if (numMoved > 0)
        	// 从指定的源数组(从指定位置开始)复制数组到目标数组的指定位置
        	// 第一个 elementData:源数组
        	// index+1:源数组的起始位置
        	// 第二个 elementData:目标数组
        	// index:目标数组的起始位置
        	// numMoved:要复制的数组元素的数量
            System.arraycopy(elementData, index+1, elementData, index, numMoved);
        // 将末尾元素置空,方便 GC
        elementData[--size] = null; // clear to let GC do its work
		// 返回要删除的值
        return oldValue;
    }

add()

这个是重点,得一步一步说,先说下 add(E e) 这个方法:


    /**
     * 将指定的元素追加到此列表的末尾。
     */
    public boolean add(E e) {
    	// 确保 elementData 数组有合适的大小
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 将 e 放入数组
        elementData[size++] = e;
        return true;
    }

再说下 ensureCapacityInternal() 这个方法:

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

不行,得先说下 calculateCapacity() 方法:

	/**
	* 计算扩容量
	*/
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
    	// 判断元素数组是否为空数组
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        	// 如果是空数组的话,返回默认容量
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

还得再说下 ensureExplicitCapacity() 方法:

	/**
	* 判断是否需要扩容
	*/
    private void ensureExplicitCapacity(int minCapacity) {
    	// 结构性调整
        modCount++;
    <span class="token comment">// overflow-conscious code</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>minCapacity <span class="token operator">-</span> elementData<span class="token punctuation">.</span>length <span class="token operator">&gt;</span> <span class="token number">0</span><span class="token punctuation">)</span>
    	<span class="token comment">// 扩容方法</span>
        <span class="token function">grow</span><span class="token punctuation">(</span>minCapacity<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

重头戏来了,必须得说下 grow() 方法了:


    /**
     * 扩容方法
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        // oldCapacity 为旧容量,newCapacity 为新容量
        int oldCapacity = elementData.length;
        // oldCapacity >> 1 运算,效果为将 oldCapacity/2;
        // 下行代码的最终效果:将新容量变为旧容量的 1.5 倍。
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 检查新容量是否比最小容量大
        if (newCapacity - minCapacity < 0)
        	// 没有最小容量大,则把最小容量作为数组的新容量
            newCapacity = minCapacity;
        // 检查新容量是否比 MAX_ARRAY_SIZE 大
        if (newCapacity - MAX_ARRAY_SIZE > 0)
        	// 如果 minCapacity 大于最大容量,则新容量 为Integer.MAX_VALUE;否则,新容量大小则为 MAX_ARRAY_SIZE。
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        // Arrays.copyof() 方法将之前的 elementData 数组弄成一个大小为 newCapacity 的数组。
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

最后说下 hugeCapacity() 方法:

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        // 若 minCapacity 大,将 Integer.MAX_VALUE 作为新数组的大小;若 MAX_ARRAY_SIZE 大,将MAX_ARRAY_SIZE作为新数组的大小
        //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
        return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
    }

加餐

System.arraycopy()

// 从指定的源数组(从指定位置开始)复制数组到目标数组的指定位置
// 第一个 elementData:源数组
// index+1:源数组的起始位置
// 第二个 elementData:目标数组
// index:目标数组的起始位置
// numMoved:要复制的数组元素的数量
System.arraycopy(elementData, index+1, elementData, index, numMoved);

Arrays.copyOf()

调用了 以下方法:

	/**
	* 复制指定的数组,返回具有指定长度的数组副本
	*/
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        // 根据 Class 的类型来决定是去 new 一个泛型数组还是说用反射去构造一个泛型数组
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
        return copy;
    }

总结

说下一个 ArrayList 从创建到扩容的完整步骤吧(以不指定初始化容量为例):

List<Integer> list = new ArrayList<>(20); 扩容几次啊?
答:一次,就初始化的时候。

  1. 初始化一个数组;
  2. 使用 add 添加进第 1 个元素到 ArrayList 中,elementData.length 为 0,size 是 0,minCapacity 为 1;
  3. 经过 calculateCapacity() 方法后,minCapacity 为 10,此时需要扩容;
  4. 经过 grow() 方法,newCapacity 为 10,此时 elementData.length 为 10,size 变为 1;
  5. 使用 add 添加第 2 个元素到 ArrayList 中时,minCapacity 为 2,此时 elementData.length 为 10,minCapacity - elementData.length > 0 不成立,不会走 grow() 方法,数组容量为 10;
  6. 使用 add 添加第 3、4、5……10 个元素时,仍不会执行 grow 方法,数组容量为 10;
  7. 当使用 add 添加第 11 个元素时,minCapacity 为 11,minCapacity - elementData.length > 0 成立,使用 grow() 方法扩容;
  8. 进入 grow() 方法,minCapacity 为 11,oldCapacity 为 10,newCapacity 为 15,数组容量扩为 15;
  9. ……

推荐阅读:

ArrayList 源码分析

ArrayList 源码分析-田小波的博客

看不懂你去打原博主别找我的 ArrayList 源码分析

关联阅读:

线性表

LinkedList 源码分析

ArrayList 与 LinkedList 区别

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值