Java - Anatomy of an ArrayList

Java - Anatomy of an ArrayList


1. Basic usage

ArrayList is a generic container. When creating a new ArrayList, you need to instantiate generic parameters, as follows:

ArrayList<Integer> intList = new ArrayList<Integer>();
ArrayList<String> strList = new ArrayList<String>();

Commonly used methods in ArrayList are as follows:

public boolean add(E e) // 添加元素到末尾
public boolean isEmpty() //判断是否为空
public int size() //获取元素个数
public E get(int index) //访问指定位置的元素
public int indexOf(Object o) // 查找指定元素,如果找到返回索引位置,否则返回-1
public int lastIndexOf(Object o) //  从后往前找
public boolean contains(Object o) // 是否判断指定元素,判断依据是调用 equals 方法
public E remove(int index) // 删除指定位置的元素,返回值为删除的元素
//删除指定对象,只删除第一个相同的对象,返回值为被删对象
//如果o为null,则删除值为null的元素
public boolean remove(Object o)
public void clear() // 清空所有元素
// 在指定位置插入元素, index 为0表示插入到最前面,index为ArrayList的长度则表示插入到最后面
public void add(int index, E element)
public E set(int index, E element) // 修改指定位置的元素内容

2. Basic principles

There is an array elementData inside the ArrayList, in general there are some reserved openings, and an integer size records the actual number of elements.

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

The internal operations of the methods of this class are basically this array and this integer, where elementData will be reallocated as the number of actual elements increases, and size is used to record the actual number of elements.

Next, look at the implementation of the addmethod , whose code is:

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

The method is first called ensureCapacityInternalto ensure that the array capacity is sufficient, which is implemented as:

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

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

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

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

First call calculateCapacityto return the minimum capacity required now, then enter the ensureExplicitCapacitymethod to judge whether the existing capacity is sufficient, if not, call the growmethod to expand the array, which is implemented as follows:

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

It oldCapacity >> 1can be seen that the original array capacity is divided by 2, so each expansion is 1.5 times the original capacity. If it is found that the expansion is not enough after 1.5 times, the expansion will be directly expanded to minCapacity, and at the same time, the boundary will be judged on the amount of expansion, and the data elements will be copied after legal, among them MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8.

Next we look removeat the implementation of the method

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

First the bounds are checked to get the element. Then calculate the number of elements that need to be moved, the elements after the index need to be moved, and then call System.arraycopythe method to move the elements. After the element movement is completed, use elementData[--size] = null, set size - 1 and set the last element to null, so that the original object is no longer referenced, which is convenient for garbage collection.

Third, the interface implemented by ArrayList

ArrayList implements three main interfaces:

  • Collection
  • List
  • RandomAccess

3.1 Collection

Collection represents a collection of data. There is no concept of position or order between data. The interface is defined as:

public interface Collection<E> extends Iterable<E> {
    int size();
    boolean isEmpty();
    boolean contains(Object o);
    Iterator<E> iterator();
    Object[] toArray();
    <T> T[] toArray(T[] a);
    boolean add(E e);
    boolean remove(Object o);
    boolean containsAll(Collection<?> c);
    boolean addAll(Collection<? extends E> c);
    boolean removeAll(Collection<?> c);
    boolean retainAll(Collection<?> c);
    void clear();
    boolean equals(Object o);
    int hashCode();
}

Java 8 adds several default methods to the Collection interface, including removeIf, stream, spliterator, etc. For details, see the API documentation.

3.2 List

List represents a collection of data in order or position. It extends Collection. The main methods added are (Java 7):

boolean addAll(int index, Collection<? extends E> c);
E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
int indexOf(Object o);
int lastIndexOf(Object o);
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
List<E> subList(int fromIndex, int toIndex);

3.3 RandomAccess

RandomAccess is defined as:

public interface RandomAccess {
}

Such an interface without any code is called a marker interface in Java and is used to declare a property of a class.

A class that implements the RandomAccess interface indicates that it can be accessed randomly. Random access has the characteristics of an array. The data is stored continuously in the memory, and the specific element can be directly located according to the index value, and the access efficiency is very high.

For example, there is a method binarySearch in the Collections class, which performs binary search in the List. Its implementation code adopts different implementation mechanisms according to whether the list implements RandomAccess, as shown below:

public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
    if(list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
        return Collections.indexedBinarySearch(list, key);
    else
        return Collections.iteratorBinarySearch(list, key);
}

4. Summary

ArrayList, which is characterized by the internal implementation of dynamic arrays, has the following characteristics:

  1. It can be accessed randomly, and it is very efficient to access according to the index position. In terms of the algorithm description, the efficiency is O(1)
  2. Unless the array is sorted, it is relatively inefficient to find elements by content, specifically O(N), where N is the length of the array content, that is, the performance is proportional to the length of the array.
  3. The efficiency of adding elements is ok, the overhead of reallocating and copying the array is amortized, specifically, the efficiency of adding N elements is O(N).
  4. Inserting and removing elements is relatively inefficient because elements need to be moved, which is O(N).

To be clear, ArrayList is not thread safe. In addition, you need to understand that there is also a class Vector, which is one of the earliest container classes implemented in Java, and also implements the List interface. Below, ArrayList is recommended.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325944401&siteId=291194637