ArrayList源码分析

1 ArrayList继承关系

ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable
在这里插入图片描述

  • 为什么ArrayList继承了AbstractList还要再实现List接口?

    1. 增加可阅读性,显式的实现List 接口
    2. 作者犯了错误,后来也就没有修改
  • RandomAccess 是一个标志接口,表示实现这个接口的List集合是支持快速随机访问的。该接口没有定义任何方法public interface RandomAccess {}

  • 实现Cloneable,覆盖了clone()方法,能被克隆。

  • 实现Serializable接口,意味着支持序列化,能通过序列化传输。

2 扩容机制

以Java11源码进行分析。

2.1 三种构造函数
  	private static final int DEFAULT_CAPACITY = 10;	// 默认容量大小
    private static final Object[] EMPTY_ELEMENTDATA = {};
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    transient Object[] elementData;	// 保存ArrayList数据的数组
    private int size; // ArrayList列表的大小,非elementData数组的大小

    /**
     * Constructs an empty list with the specified initial capacity.
     * @param  initialCapacity the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    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);
        }
    }

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

   /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's iterator.
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        Object[] a = c.toArray();
        if ((size = a.length) != 0) {
            if (c.getClass() == ArrayList.class) {
                elementData = a;
            } else {
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // c为空 replace with empty array.
            elementData = EMPTY_ELEMENTDATA;
        }
    }

我们从源码中看到,使用无参构造方法时,实际上初始化赋值的是一个空数组,但为什么代码注释写的是Constructs an empty list with an initial capacity of ten.呢。实际上,当真正对数组操作时,才真正分配容量。即向数组添加第一个元素时,数组容量扩为10。这会在后续代码中展现出来。

2.2 add方法

add()方法添加的是单个元素。

    /**
     * 在列表尾端添加一个元素 (可供外部调用的接口)
     */
    public boolean add(E e) {
        modCount++; //
        add(e, elementData, size);
        return true;
    }
    /**
     * This helper method split out from add(E) to keep method (实际就是add()方法的帮手)
     */
    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length) // 如果数组容量恰好等于List的size
            elementData = grow(); // 扩容
        elementData[s] = e;
        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();
        System.arraycopy(elementData, index,
                         elementData, index + 1,
                         s - index);
        elementData[index] = element;
        size = s + 1;
    }

我们从源码中可以看出:添加单个数据的add方法共有三个

  • 从尾部添加数据时,实际调用的是内部方法pivate void add(E e, Object[] elementData, int s), 如果elementData数组容量恰好已满,则用grow()扩容(2.3节),否则直接在数组中添加数据。
  • 在指定位置添加单个数据,用private void rangeCheckForAdd(int index)检查,
2.3 grow方法
    private Object[] grow() {
        return grow(size + 1);
    }
	/**
     * Increases the capacity to ensure that it can hold at least the number of elements specified by the minimum capacity argument.
     */
    private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity));
    }
  • 源码中共有两个grow方法,实际上最后返回的是扩容后新的数组
  • 无参grow()方法均在添加单个元素而空间恰好满的时候使用,实际上调用了第二个grow(int minCapacity)方法,告诉其当前最小需要的容量是size + 1
  • 有参grow(int minCapacity)方法根据当前数组最小需要的容量minCapacity来调用newCapacity(minCapacity)2.4节)来判断具体应该扩容的多少。
2.4 newCapacity方法

ArratList扩容的核心方法。(jdk1.8的时候是grow()方法)


    /**
     * 返回至少比minCapacity大的容量
     * 1.5倍扩容
     * Will not return a capacity greater than MAX_ARRAY_SIZE unless
     * the given minimum capacity is greater than MAX_ARRAY_SIZE.
     */
    private int newCapacity(int minCapacity) {
        // oldCapacity为原数组容量,newCapacity扩容为原来的1.5倍
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity <= 0) { // 如果新容量还小于所需最小容量
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) //是否原数组是空数组,若是,则返回10或者所需最小容量的较大值
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // 如果所需最小容量为负,可能是由于addAll方法,导致Integer越界变成负的了
                throw new OutOfMemoryError();
            return minCapacity;
        }
        // 如果新容量大于所需最小容量,判断新容量是否超过数组最大长度`MAX_ARRAY_SIZE 
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        // MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
        return (minCapacity > MAX_ARRAY_SIZE)
            ? Integer.MAX_VALUE
            : MAX_ARRAY_SIZE;
    }
  • newCapacity可以看出来,当列表第一次add元素时,oldCapacity为0,则newCapacity也为0,小于minCapacity,此时elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,会返回minCapacity和10中的较大值。
  • 如果新容量大于MAX_ARRAY_SIZE则会执行hugeCapacity()来比较minCapacityMAX_ARRAY_SIZE

3 System.arraycopy 和 Arrays.copyOf

ArrayList中很多地方都用到了这两个方法。

System.arraycopy()方法

	/**
     * @param      src      源数组.
     * @param      srcPos   源数组开始位置.
     * @param      dest     目标数组.
     * @param      destPos  目标数组开始位置.
     * @param      length   拷贝的长度
     */
    @HotSpotIntrinsicCandidate
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

Arrays.copyOf()方法

	/**
     * @param 要复制的数组
     * @param 要复制的长度
     */
    @HotSpotIntrinsicCandidate
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        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;
    }

联系与区别

联系:
Arrays.copyOf() 内部实际调用了 System.arraycopy()
区别:
System.arraycopy()需要手动传入目标数组,Arrays.copyOf()是自己创建一个新数组,然后将原数组拷贝进去再返回。

4 ensureCapacity()方法

ArrayList源码中有一个ensureCapacity()方法,但很奇怪,在内部方法中却没有得到任何调用,而且声明为public,似乎是给用户调用的,但好像用户也不怎么用这个方法。

   /**
	 *如有必要,对ArrayList进行扩容	
     */
    public void ensureCapacity(int minCapacity) {
        if (minCapacity > elementData.length
            && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
                 && minCapacity <= DEFAULT_CAPACITY)) {
            modCount++;
            grow(minCapacity);
        }
    }

其实,在添加大量元素前,用户可以手动调用ensureCapacity()方法,以减少数组重新扩容拷贝的开销。

4 modCount变量

通过源码发现,modCount 字段不容忽视。
protected transient int modCount = 0
参考 Java集合框架学习(4)——ArrayList中的modCount变量

利用迭代器遍历ArrayList的时候,如果用ArrayList.remove方法删除元素,就会引发异常,其实就是modCount的作用。

其实就是

5 线程安全问题

在这里插入图片描述

  • ArrayListList 的主要实现类,底层使用 Object[ ]存储,适用于频繁的查找工作,线程不安全 ;
  • VectorList的古老实现类,底层使用 Object[ ]存储,线程安全的。
  • CopyOnWriteArrayList (java.util.concurrent包)可以看作是线程安全的 ArrayList,在读多写少的场合性能非常好,远远好于 Vector.
public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    /**
     * The lock protecting all mutators.  (We have a mild preference
     * for builtin monitors over ReentrantLock when either will do.)
     */
    final transient Object lock = new Object();

    /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;  // violatile保证可见性
}

在这里插入图片描述

图参考 CopyOnWriteArrayList你都不知道,怎么拿offer?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值