Analysis of the working principle of ArrayList in JDK8


ArrayList is also a class that is used very frequently in Java development, and it is internally implemented based on the dynamic management of arrays. The array is a continuous storage space in memory, and its advantage is that random access and traversal based on subscripts are very efficient.

The ArrayList class structure in the JDK8 source code is defined as follows:

````
 class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
````


(1) Inherited AbstractList, implemented the List interface as an array queue, and had the basic addition, deletion, modification and query function of List

(2) Implemented the RandomAccess interface with random read and write functions

(3) Implemented the Cloneable interface can be cloned

(4) Implements the Serializable interface and rewrites the serialization and deserialization methods, so that ArrayList can have better serialization performance.



The member variables and several constructors in ArrayList are as follows:

````java
 //The defined serialization id, mainly to identify the compatibility of different versions    
 private static final long serialVersionUID = 8683452581122892189L;
 //default array storage capacity
  private static final int DEFAULT_CAPACITY = 10;
  / / When the capacity of the specified array is 0, use this variable to assign a value
  private static final Object[] EMPTY_ELEMENTDATA = {};
  //Use this variable to assign values ​​when instantiating by default
  private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  //The object array that actually stores the data is not serialized
  transient Object[] elementData;
  //The actual number of elements in the array is less than or equal to elementData.length
  private int size;
  //Maximum number of elements in the array
  private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    //Constructor 1, if the specified capacity is allocated, the size of the specified capacity is allocated
    //Use EMPTY_ELEMENTDATA assignment without specifying
      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);
        }
    }    
    
    //Constructor 2, use the default DEFAULTCAPACITY_EMPTY_ELEMENTDATA assignment
        public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    //Construct an incoming collection as the data of the array
        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;
        }
    }


````


After understanding its member variables and constructors, let's take a look at several commonly used methods:

(1)

Add There are two methods for adding. The call chain of the first add(E e) method involves 5 methods, respectively. as follows:

````
  //1
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    //2
        private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
    //3
        private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    //4
        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);
    }
    
    //5
        private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
    
````
Here is a step-by-step analysis. In the first step of calling the add(E e) method, we see that it calls the ensureCapacityInternal(size + 1) method. In this method, we first judge whether the array is an empty array with a length of 0. , if so, assign its capacity to the default capacity size of 10, and then call the ensureExplicitCapacity method, which records modCount+1, and judges whether the current capacity is less than the current length of the array, if it is greater than the current The length of the array begins to expand the operation and call the method grow(minCapacity). The length of the expansion is increased by half the size of the original array, and then it is judged whether the upper limit of the expansion of the array has been reached and assigned, and then the data of the old array is copied to The new array after expansion is assigned to the old array again, and finally the newly added element is assigned to the position of size+1 after expansion.


Then look at the second add method:

````
    public void add(int index, E element) {
        rangeCheckForAdd(index);//Whether it is out of bounds

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


The System.arraycopy method is used here, and the meaning of the parameters is as follows:

(Original array, the starting position of the original array, the target array, the starting position of the target array, the number of copies)

(Note: If you want to know about the array copy in Java There are several ways, please refer to my previous article.)

This is mainly to add an element to the specified position, ArrayList first checks whether the index is out of bounds, if not, it checks whether it needs to be expanded, and then all the data after the index position , the whole is copied to the starting position of index+1, and then the newly added data can be placed at the position of index, and the data in front of the index does not need to be moved. Here we can see that inserting data into the specified position ArrayList is a large Actions consume more performance.




(2) Remove

(1) Remove according to the subscript

````java
    public E remove(int index) {
        //check for out of bounds
        rangeCheck (index);
        // record the number of changes
        modCount++;
        //Get the value at the removed position
        E oldValue = elementData(index);
        //Get the number of elements to move
        int numMoved = size - index - 1;
        if (numMoved > 0)
        //Copy all the data moved to the index position
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //Assign the element at the position of size-1 to null, which is convenient for gc
        elementData[--size] = null; // clear to let GC do its work
        //finally return the old data
        return oldValue;
    }
````


(2) Remove according to elements
````
    public boolean remove(Object o) {
    //Remove the value equal to null
        if (o == null) {
         // loop through the array
            for (int index = 0; index < size; index++)
            //find the first element in the collection that is equal to null
                if (elementData[index] == null) {
                //then remove
                    fastRemove(index);
                    return true;
                }
        } else {
        //In the case of non-null, traverse each element and compare it by equals
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                //then remove
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

//This method is the same as the principle of removing by subscript, and the overall left shift
    private void fastRemove(int index) {
        modCount++;
        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
    }

````



The remove method and add(int index, E element) are exactly the opposite operation process. Removing an element will affect the position movement of a batch of data, so it is also relatively performance-intensive.


(3) Inquiry

````
    public E get(int index) {
      //check for out of bounds
        rangeCheck (index);
        //return the element at the specified position
        return elementData(index);
    }
````


(4) Modification

````
    public E set(int index, E element) {
    //check for out of bounds
        rangeCheck (index);
        //get the old element value
        E oldValue = elementData(index);
        // new element assignment
        elementData[index] = element;
        //return the old element value
        return oldValue;
    }
````


(5) Empty method
````
    public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }
````

The clear method is to assign the value of each element to null, which is convenient for gc recycling


(6) slimming method
````
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }
````
This method mainly reduces the space of the array and removes the null value in the array.
Arrays.copyOf method parameter meaning: (original array, number of copies)

(7) Whether it contains
````java
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }
    
        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;
    }
````
There are mainly two cases of null value traversal and non-null traversal. If the query is found, it returns the subscript position, otherwise it returns -1, and then compared with 0, if it is greater than 0, it exists, and if it is less than 0, it does not exist. .


Summary:


This article introduces the working principle and common method analysis of ArrayList in JDK8. In addition, ArrayList is not thread-safe, so in scenarios where multi-threading is required, please use the concurrent List structure provided by jdk or the List provided by toolkits such as Guava and Apache Common. gather. Array-based List is more efficient in random access and traversal, but inefficient when inserting and deleting specified elements, which is just the opposite of linked list. Linked list query and random traversal are less efficient, but insertion and It is more efficient to delete elements at the specified position, which is why the two data structures are used in HashMap to complement each other.



If you have any questions, you can scan the code and follow the WeChat public account: I am the siege division (woshigcs), leave a message in the background for consultation. Technical debts cannot be owed, and health debts cannot be owed. On the road of seeking the Tao, walk with you.


Guess you like

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