Java11 ArrayList类详解

ArrayList

ArrayList是List接口的可变大小数组的实现。实现所有可选链表操作,允许所有元素,包括null。除了实现了List接口之外,这个类还提供了操作用来内部存储链表的数组大小的方法(这个类大致等同于Vector,除了他是异步的以外)。

size()、isEmpty()、get()、set()、iterator()、listIterator()操作以恒定的时间运行。add()操作以O(n)的时间复杂度运行,其他的所有操作则以线性的时间运行(粗略的说)。常量因子相比与LinkedList较低。

每个ArrayList实例有一个capacity(容量)。capacity是用来存储链表元素的数组的大小。它总是至少和list大小一样大。当元素被加入到ArrayList后,它的capacity会自动扩容。除了增加一个元素有恒定的摊销时间成本之外,增长策略的细节未被指定。

在添加大量的元素之前,一个应用可以使用ensureCapacity()操作提高一个实例的capacity。这可以减少增量重分配的数量(This may reduce the amount of incremental reallocation)。

要注意的是ArrayList不是同步的。如果多线程并发的访问一个ArrayList实例,至少有一个线程会修改链表结构,必须在它的外部进行异步控制。(一个结构化的修改是指增加或删除一个或多个元素的操作,或者显式改变底层数组的大小;仅仅设置一个元素的值不是一个结构化的修改。)一个典型的实现是通过对某个对象的同步来自然封装这个链表。(This is typically accomplished by synchronizing on some object that naturally encapsulates the list.)

如果没有这种对象,这个链表则需要用Collections.synchronizedList()方法来封装。这最好是在创建的时候完成,以防止偶然的对链表的异步访问。

List list = Collections.synchronizedList(new ArrayList(...));

类的iterator()和listIterator(int)方法返回的迭代器都是fail-fast的:如果迭代器创建之后,链表在任意时刻被结构化的修改,除了迭代器自己的remove()、add()方法,迭代器将抛出ConcurrentModificationException异常。因此,面对并发修改,迭代器将快速失败并清空,而不是在未来的不确定的时刻冒着任意的风险以及不确定的行为。

注意,迭代器的fail-fast行为不能被保证一定发生,通常来说,当异步并发修改发生时,不可能做出任何硬性保证。Fail-fast迭代器基于最大努力抛出ConcurrentModificationException异常。因此,这样的做法的是错的,写一个程序,依赖于这个异常来保证程序的正确性:迭代器的fail-fast行为应该只被用来检查bug(个人理解是如果发生并发修改,并不一定保证抛出ConcurrentModificationException异常,因此程序的正确性不应该依赖此异常)。

1.默认容量capacity

/**
 * Default initial capacity.
*/ private static final int DEFAULT_CAPACITY = 10;

2.存储ArrayList元素的数组elementData

/**
 * 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

ArrayList的容量就是数组的长度。

任何elementData等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList,当其第一个元素被添加进来时,elementData的长度将被扩展为DEFAULT_CAPACITY。

3.ArrayList包含元素的数量size

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

4.构造器

ArrayList包含三种构造器:

1.ArrayList():将elementData初始化为空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA。

2.ArrayList(int initialCapacity):将element初始化为容量为initialCapacity的数组,当initialCapacity为0是,初始化为EMPTY_ELEMENTDATA

3.ArrayList(Collection<? extends E> c):使用集合来初始化elementData,如果集合的length为0,则初始化为EMPTY_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) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // defend against c.toArray (incorrectly) not returning Object[]
            // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

5.Fast-fail与结构化修改

ArrayList继承于AbstractList,AbstractList中的域modCount用来统计结构化修改的次数。

结构化修改是指改变链表的大小,或者其他扰乱它的方式,可能导致正在进行的迭代可能产生不正确的结果。

    /**
     * The number of times this list has been <i>structurally modified</i>.
     * Structural modifications are those that change the size of the
     * list, or otherwise perturb it in such a fashion that iterations in
     * progress may yield incorrect results.
     *
     * <p>This field is used by the iterator and list iterator implementation
     * returned by the {@code iterator} and {@code listIterator} methods.
     * If the value of this field changes unexpectedly, the iterator (or list
     * iterator) will throw a {@code ConcurrentModificationException} in
     * response to the {@code next}, {@code remove}, {@code previous},
     * {@code set} or {@code add} operations.  This provides
     * <i>fail-fast</i> behavior, rather than non-deterministic behavior in
     * the face of concurrent modification during iteration.
     *
     * <p><b>Use of this field by subclasses is optional.</b> If a subclass
     * wishes to provide fail-fast iterators (and list iterators), then it
     * merely has to increment this field in its {@code add(int, E)} and
     * {@code remove(int)} methods (and any other methods that it overrides
     * that result in structural modifications to the list).  A single call to
     * {@code add(int, E)} or {@code remove(int)} must add no more than
     * one to this field, or the iterators (and list iterators) will throw
     * bogus {@code ConcurrentModificationExceptions}.  If an implementation
     * does not wish to provide fail-fast iterators, this field may be
     * ignored.
     */
    protected transient int modCount = 0;
View Code

在序列化以及迭代(forEach)等操作的前后,需要记录modCount的值进行对比,如果不相等,则抛出ConcurrentModificationException异常。

    /**
     * Saves the state of the {@code ArrayList} instance to a stream
     * (that is, serializes it).
     *
     * @param s the stream
     * @throws java.io.IOException if an I/O error occurs
     * @serialData The length of the array backing the {@code ArrayList}
     *             instance is emitted (int), followed by all of its elements
     *             (each an {@code Object}) in the proper order.
     */
    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 behavioral 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();
        }
    }

    /**
     * @throws NullPointerException {@inheritDoc}
     */
    @Override
    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int expectedModCount = modCount;
        final Object[] es = elementData;
        final int size = this.size;
        for (int i = 0; modCount == expectedModCount && i < size; i++)
            action.accept(elementAt(es, i));
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }

6.重要的方法

  • public void ensureCapacity(int minCapacity)

  参数minCapacity是要扩容的最小大小,要进行扩容必须满足:

    1.minCapacity大于现在elementData的长度。

    2.不满足(elementData为默认空数组&&要扩容的最小大小小于DEFAULT_CAPACITY

    /**
     * Increases the capacity of this {@code ArrayList} 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) {
        if (minCapacity > elementData.length
            && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
                 && minCapacity <= DEFAULT_CAPACITY)) {
            modCount++;
            grow(minCapacity);
        }
    }
  • private int newCapacity(int minCapacity)

  返回至少和给定minCapacity一样大小的容量。

  如果可以的话将返回elementData大小的1.5倍。

  除非给定minCapacity大于MAX_ARRAY_SIZE,否则容量不应超过MAX_ARRAY_SIZE。

  /**
     * Returns a capacity at least as large as the given minimum capacity.
     * Returns the current capacity increased by 50% if that suffices.
     * Will not return a capacity greater than MAX_ARRAY_SIZE unless
     * the given minimum capacity is greater than MAX_ARRAY_SIZE.
     *
     * @param minCapacity the desired minimum capacity
     * @throws OutOfMemoryError if minCapacity is less than zero
     */
    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
          //之前介绍elementData的时候说到,elementData为DEFAULTCAPACITY_EMPTY_ELEMENTDATA
          //的空ArrayList,当第一次添加元素时,容量被扩充为DEFAULT_CAPACITY
return Math.max(DEFAULT_CAPACITY, minCapacity); if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return minCapacity; } return (newCapacity - MAX_ARRAY_SIZE <= 0) ? newCapacity : hugeCapacity(minCapacity); }
  • private Object[] grow(int minCapacity)
  • private Object[] grow()
    /**
     * 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
     * @throws OutOfMemoryError if minCapacity is less than zero
     */
    private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }

    private Object[] grow() {
        return grow(size + 1);
    }
  • private void add(E e, Object[] elementData, int s)
  • public boolean add(E e)
  • public add(int index, E element)

add(E e)执行时,根据调用关系,当要进行扩容时,最终会调用newCapacity(),容量为扩充为element.length*1.5和minCapacity中的较大者(通常情况是这样,具体参见代码newCapacity())。

add(int index, E element)执行时,如需进行扩容会先进行扩容,然后通过System.arraycopy进行移位,再将元素插入指定位置。

    /**
     * This helper method split out from add(E) to keep method
     * bytecode size under 35 (the -XX:MaxInlineSize default value),
     * which helps when add(E) is called in a C1-compiled loop.
     */
    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }

    /**
     * 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);
        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;
    }
  • public E remove(int index)
  • public boolean remove(Object o)
  • private void fastRemove(Object[] es, int i)

  remove(int index)主要调用了fastRemove()来进行删除,fastRemove()用的也是System.arraycopy()。

  remove(Object o)先找到该对象的索引,然后调用fastRemove()。

    /**
     * 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) {
        Objects.checkIndex(index, size);
        final Object[] es = elementData;

        @SuppressWarnings("unchecked") E oldValue = (E) es[index];
        fastRemove(es, index);

        return oldValue;
    }

    /**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * {@code i} such that
     * {@code Objects.equals(o, get(i))}
     * (if such an element exists).  Returns {@code true} if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return {@code true} if this list contained the specified element
     */
    public boolean remove(Object o) {
        final Object[] es = elementData;
        final int size = this.size;
        int i = 0;
        found: {
            if (o == null) {
                for (; i < size; i++)
                    if (es[i] == null)
                        break found;
            } else {
                for (; i < size; i++)
                    if (o.equals(es[i]))
                        break found;
            }
            return false;
        }
        fastRemove(es, i);
        return true;
    }

    /**
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(Object[] es, int i) {
        modCount++;
        final int newSize;
        if ((newSize = size - 1) > i)
            System.arraycopy(es, i + 1, es, i, newSize - i);
        es[size = newSize] = null;
    }

7.序列化

ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。

可以发现,elementData被transient修饰,不会被序列化。

transient 关键字

    /**
     * Saves the state of the {@code ArrayList} instance to a stream
     * (that is, serializes it).
     *
     * @param s the stream
     * @throws java.io.IOException if an I/O error occurs
     * @serialData The length of the array backing the {@code ArrayList}
     *             instance is emitted (int), followed by all of its elements
     *             (each an {@code Object}) in the proper order.
     */
    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 behavioral 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();
        }
    }

    /**
     * Reconstitutes the {@code ArrayList} instance from a stream (that is,
     * deserializes it).
     * @param s the stream
     * @throws ClassNotFoundException if the class of a serialized object
     *         could not be found
     * @throws java.io.IOException if an I/O error occurs
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {

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

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

        if (size > 0) {
            // like clone(), allocate array based upon size not capacity
            SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
            Object[] elements = new Object[size];

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

            elementData = elements;
        } else if (size == 0) {
            elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new java.io.InvalidObjectException("Invalid size: " + size);
        }
    }

猜你喜欢

转载自www.cnblogs.com/yfzhou/p/10097158.html