Java集合框架源码解读——Collection - ArrayList 源码解析

Posted 傲骄鹿先生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java集合框架源码解读——Collection - ArrayList 源码解析相关的知识,希望对你有一定的参考价值。

​​​​​​​我是傲骄鹿先生,沉淀、学习、分享、成长。

如果你觉得文章内容还可以的话,希望不吝您的「一键三连」,文章里面有不足的地方希望各位在评论区补充疑惑、见解以及面试中遇到的奇葩问法​​​​​​​

目录

概述

ArrayList的实现

        底层数据结构

        构造函数

        自动扩容

        add(), addAll()

        set()

        get()

        remove()

        trimToSize()

        indexOf(), lastIndexOf()

        Fail-Fast机制:


概述

List接口是可调整大小的数组实现。实现所有可选列表操作,并允许所有元素,包括 null。除了实现List接口之外,该类还提供了一些方法来操作内部用于存储列表的数组的大小。(这个类大致相当于 Vector,除了它是不同步的。)

 size、isEmpty、get、set、iterator和 listIterator操作在恒定时间内运行。添加操作以摊销常数时间运行,添加 n 个元素需要 O(n) 时间。所有其他操作都以线性时间运行(粗略地说)。与LinkedList实现 相比,常数因子较低。

每个ArrayList实例都有一个容量。容量是用于存储列表中元素的数组的大小。它总是至少与列表大小一样大。随着元素被添加到 ArrayList,它的容量会自动增长。除了添加一个元素具有恒定的摊销时间成本这一事实之外,没有指定增长策略的细节。

应用程序可以在使用ensureCapacity 操作添加大量元素之前增加ArrayList实例的容量。这可以减少增量重新分配的数量。

请注意,此实现不同步。 如果多个线程同时访问一个ArrayList实例,并且至少有一个线程在结构上修改了列表,则 必须在外部进行同步。(结构修改是添加或删除一个或多个元素,或显式调整后备数组大小的任何操作;仅设置元素的值不是结构修改。)这通常通过同步一些自然封装的对象来完成列表。如果不存在这样的对象,则应使用该 Collections.synchronizedList 方法“包装”列表。这最好在创建时完成,以防止对列表的意外不同步访问:

   列表列表 = Collections.synchronizedList(new ArrayList(...));

此类的iterator和 listIterator方法返回的迭代器是快速失败的:如果在创建迭代器后的任何时候列表在结构上被修改,除了通过迭代器自己的 remove或 add方法之外的任何方式,迭代器将抛出一个 ConcurrentModificationException. 因此,面对并发修改,迭代器快速而干净地失败,而不是在未来不确定的时间冒任意的、非确定性的行为。

请注意,不能保证迭代器的快速失败行为,因为一般来说,在存在不同步的并发修改的情况下,不可能做出任何硬保证。快速失败的迭代器ConcurrentModificationException在尽力而为的基础上抛出。因此,编写一个依赖于这个异常的正确性的程序是错误的: 迭代器的快速失败行为应该只用于检测错误。

此类是 Java Collections Framework的成员。

ArrayList的实现

底层数据结构

	/**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * The size of the ArrayList (the number of elements it contains).
     */
    private int size;

构造函数

/**
     * 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 
            // replace with empty array.
            elementData = EMPTY_ELEMENTDATA;
        
    

自动扩容

每当向数组中添加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。数组扩容通过一个公开的方法ensureCapacity(int minCapacity)来实现。在实际添加大量元素前,也可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。

数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。

/**
     * Increases the capacity of this <tt>ArrayList</tt> instance, if
     * necessary, to ensure that it can hold at least the number of elements
     * specified by the minimum capacity argument.
     *
     * @param   minCapacity   the desired minimum capacity
     */
    public void ensureCapacity(int minCapacity) 
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) 
            ensureExplicitCapacity(minCapacity);
        
    

    private static int calculateCapacity(Object[] elementData, int minCapacity) 
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) 
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        
        return minCapacity;
    

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

    private void ensureExplicitCapacity(int minCapacity) 
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    

    /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    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);
    

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

 add(), addAll()

跟C++ 的vector不同,ArrayList没有push_back()方法,对应的方法是add(E e)ArrayList也没有insert()方法,对应的方法是add(int index, E e)。这两个方法都是向容器中添加新元素,这可能会导致capacity不足,因此在添加元素之前,都需要进行剩余空间检查,如果需要则自动扩容。扩容操作最终是通过grow()方法完成的。

/**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException @inheritDoc
     */
    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++;
    
/**
     * Appends all of the elements in the specified collection to the end of
     * this list, in the order that they are returned by the
     * specified collection's Iterator.  The behavior of this operation is
     * undefined if the specified collection is modified while the operation
     * is in progress.  (This implies that the behavior of this call is
     * undefined if the specified collection is this list, and this
     * list is nonempty.)
     *
     * @param c collection containing elements to be added to this list
     * @return <tt>true</tt> if this list changed as a result of the call
     * @throws NullPointerException if the specified collection is null
     */
    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;
    

    /**
     * Inserts all of the elements in the specified collection into this
     * list, starting at the specified position.  Shifts the element
     * currently at that position (if any) and any subsequent elements to
     * the right (increases their indices).  The new elements will appear
     * in the list in the order that they are returned by the
     * specified collection's iterator.
     *
     * @param index index at which to insert the first element from the
     *              specified collection
     * @param c collection containing elements to be added to this list
     * @return <tt>true</tt> if this list changed as a result of the call
     * @throws IndexOutOfBoundsException @inheritDoc
     * @throws NullPointerException if the specified collection is null
     */
    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;
    

set()

既然底层是一个数组ArrayListset()方法也就变得非常简单,直接对数组的指定位置赋值即可。

/**
     * Replaces the element at the specified position in this list with
     * the specified element.
     *
     * @param index index of the element to replace
     * @param element element to be stored at the specified position
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException @inheritDoc
     */
    public E set(int index, E element) 
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    

get()

get()方法同样很简单,唯一要注意的是由于底层数组是Object[],得到元素后需要进行类型转换。

/**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException @inheritDoc
     */
    public E get(int index) 
        rangeCheck(index);

        return elementData(index);
    

remove()

remove()方法也有两个版本,一个是remove(int index)删除指定位置的元素,另一个是remove(Object o)删除第一个满足o.equals(elementData[index])的元素。删除操作是add()操作的逆过程,需要将删除点之后的元素向前移动一个位置。需要注意的是为了让GC起作用,必须显式的为最后一个位置赋null值。

/**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException @inheritDoc
     */
    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;
    

关于Java GC这里需要特别说明一下,有了垃圾收集器并不意味着一定不会有内存泄漏。对象能否被GC的依据是是否还有引用指向它,上面代码中如果不手动赋null值,除非对应的位置被其他元素覆盖,否则原来的对象就一直不会被回收

trimToSize()

ArrayList还给我们提供了将底层数组的容量调整为当前列表保存的实际元素的大小的功能。它可以通过trimToSize方法来实现。代码如下:

/**
     * Trims the capacity of this <tt>ArrayList</tt> instance to be the
     * list's current size.  An application can use this operation to minimize
     * the storage of an <tt>ArrayList</tt> instance.
     */
    public void trimToSize() 
        modCount++;
        if (size < elementData.length) 
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        
    

indexOf(), lastIndexOf()

获取元素的第一次出现的index:

/**
     * Returns the index of the first occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the lowest index <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
     * or -1 if there is no such index.
     */
    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;
    

获取元素的最后一次出现的index:

    /**
     * Returns the index of the last occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the highest index <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
     * or -1 if there is no such index.
     */
    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;
    

Fail-Fast机制:

ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。

系列文章持续更新,微信搜一搜「傲骄鹿先生 」,回复【面试】有准备的一线大厂面试资料。

以上是关于Java集合框架源码解读——Collection - ArrayList 源码解析的主要内容,如果未能解决你的问题,请参考以下文章

Java集合框架源码解读——Collection - LinkedList源码解析

Java集合框架源码解读——Collection - LinkedList源码解析

Java集合框架源码解读——Collection - LinkedList源码解析

源码阅读Java集合 - ArrayList深度源码解读

深入理解JAVA集合系列四:ArrayList源码解读

深入理解JAVA集合系列四:ArrayList源码解读