[Collection - ArrayList source code analysis]


This article mainly analyzes the source code of Collection-ArrayList.

 Overview

ArrayList implements the List interface, which is a sequential container, that is, the data stored in the elements and the The order is the same, allowing null elements to be placed, and the underlying implementation is through the array. Except that this class does not implement synchronization, the rest is roughly the same as Vector. EachArrayList has a capacity (capacity), which represents the actual size of the underlying array. The number of elements stored in the container cannot exceed the current capacity. When elements are added to the container, the container automatically increases the size of the underlying array if there is insufficient capacity. As mentioned before, Java generics are just syntax sugar provided by the compiler, so the array here is an Object array to be able to accommodate any type of object.

The size(), isEmpty(), get(), and set() methods can all be completed in constant time. The time cost of the add() method is related to the insertion position, and the time cost of the addAll() method is proportional to the number of added elements. Proportional. Most of the other methods are linear time.

In pursuit of efficiency, ArrayList does not implement synchronization. If concurrent access by multiple threads is required, users can synchronize manually or use Vector instead.

 Implementation of ArrayList

 underlying data structure

	/**
     * 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).
     *
     * @serial
     */
    private int size;

 Constructor

	/**
     * 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) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

 Automatic expansion

Whenever an element is added to the array, it is necessary to check whether the number of added elements exceeds the length of the current array. If so, the array will be expanded to meet the needs of the added data. Array expansion is achieved through a public method ensureCapacity(int minCapacity). I can also use ensureCapacity to manually increase the capacity of the ArrayList instance before actually adding a large number of elements to reduce the amount of incremental reallocation.

When the array is expanded, the elements in the old array will be copied to the new array. Each time the array capacity increases, it will be approximately 1.5 times its original capacity. The cost of this operation is very high, so in actual use, we should try to avoid the expansion of the array capacity. When we can predict the number of elements to be saved, we must specify its capacity when constructing the ArrayList instance to avoid array expansion. Or according to actual needs, manually increase the capacity of the ArrayList instance by calling the ensureCapacity method.

    /**
     * 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 void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(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()

Different from C++'svector, ArrayList does not have< /span> does not have ArrayList, push_back() method, the corresponding method is . Both methods add new elements to the container, which may result in insufficient capacity, so the remaining space needs to be checked before adding elements. , automatically expand if necessary. The expansion operation is finally completed through the method. add(E e)insert()add(int index, E e)grow()

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        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);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

add(int index, E e)The element needs to be moved first and then the insertion operation is completed, which means that this method has linear time complexity.

addAll()The method can add multiple elements at one time, and there are two versions depending on the position. One is added at the end addAll(Collection<? extends E> c) method, and the other is inserted starting from the specified position addAll(int index, Collection<? extends E> c)method. Similar to the add() method, space check is also required before insertion, and the capacity will be automatically expanded if necessary; if inserted from a specified position, elements may also be moved. The time complexity of addAll() is not only related to the number of inserted elements, but also to the insertion position.

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

Since the bottom layer is an arrayArrayList’sset() method becomes very simple, just use the array directly Just assign a value to the specified location.

public E set(int index, E element) {
    rangeCheck(index);//下标越界检查
    E oldValue = elementData(index);
    elementData[index] = element;//赋值到指定位置,复制的仅仅是引用
    return oldValue;
}

 get()

get()The method is also very simple. The only thing to note is that since the underlying array is Object[], type conversion is required after getting the elements.

public E get(int index) {
    rangeCheck(index);
    return (E) elementData[index];//注意类型转换
}

 remove()

remove()The method also has two versions, one is remove(int index) to delete the element at the specified position, and the other is remove(Object o) to delete the first element that satisfies < a element of i=3>. The deletion operation is the reverse process of the operation, which requires moving the element after the deletion point forward one position. It should be noted that in order for GC to work, the last position must be explicitly assigned a value of . o.equals(elementData[index])add()null

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; //清除该位置的引用,让GC起作用
    return oldValue;
}

A special note needs to be made about Java GC.Having a garbage collector does not mean that there will be no memory leaks. Whether an object can be GCd depends on whether there are still references pointing to it. If you do not manually assign the null value in the above code, unless the corresponding position is overwritten by other elements, the original object will never be Be recycled.

 trimToSize()

ArrayList also provides us with the function of adjusting the capacity of the underlying array to the size of the actual elements stored in the current list. It can be achieved through trimToSize method. code show as below:

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

Get the index of the first occurrence of an element:

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

Get the index of the last occurrence of an element:

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

ArrayList also uses a fast failure mechanism, which is implemented by recording the modCount parameter. The iterator quickly fails completely when faced with concurrent modifications, rather than risking arbitrary unspecified behavior at an unspecified time in the future.


Guess you like

Origin blog.csdn.net/abclyq/article/details/134744842