Learn the List set in the Java set from the source code, detailed and thorough, in one step

Posted by paulsiew2 on Wed, 02 Feb 2022 11:34:13 +0100

summary

A List is an ordered set (also known as a sequence). Users of this interface have precise control over where each element in the List is inserted. Users can access elements through their integer index (position in the List) and search for elements in the List. In fact, it is just a idiom to say that it is a sub interface of the Collection interface (Collection has many sub interfaces, which is one of the three main sub interfaces, and the other two will be mentioned later). Therefore, the methods defined in the Collection interface can also be used in the List interface. In addition, according to the characteristics of List, Other methods are introduced.

Features of List interface:

  • Elements are stored in a linear manner
  • Element access is orderly, that is, the storage order of elements is consistent with the extraction order.
  • The element has an index. Through the index, you can accurately operate the elements in the collection (similar to arrays)
  • The element can be repeated. The equals method of the element is used to compare whether it is a repeated element

Use of List

Common methods of List

Basic introduction

Common methods mentioned here refer to methods other than implementing the Collection interface. As mentioned earlier, the elements in the List set can be operated by index, so some methods to operate the elements of the set according to the index are added to the List set. These methods are briefly introduced below:

  • void add(iint index, E element): inserts the specified element at the specified position in the list
  • Boolean addall (int index, collection <? Extensions E > C): inserts all elements in the specified collection into the list at the specified location
  • E get(int index): returns the element at the specified position in this list
  • List subList(int fromIndex, int toIndex): returns the set of some objects in the list, that is, the returned set is a subset of the list and is the value of the subscript index. The parent set list starts with fromIndex (included) and ends with toIndex (not included) as the returned subset
  • int indexOf(Object obj): returns the index of the first occurrence of the specified element in the list. If the list does not contain elements, returns - 1
  • int lastIndexOf(Object obj): returns the index of the last occurrence of the specified element in the list. If the list does not contain elements, returns - 1
  • E remove(int index): removes the element at the specified position in this list
  • E set(int index, E element): replace the element at the specified position in this list with the specified element

Code example

public class ListDemo {
    public static void main(String[] args) {
		// Create a List collection object through the implementation class ArrayList of List
    	List<String> list = new ArrayList<String>();

    	// Add element at specified location
    	list.add(0,"jack");
    	list.add(1,"rose");	
    	list.add(2,"marry");		
    	System.out.println(list);
    	
    	// Delete element with index position 2 
    	list.remove(2);    	
    	System.out.println(list);
    	
    	// The specified element replaces the element at the specified position in this list
    	list.set(0, "Lao Wang");
    	System.out.println(list);
    	
    	// Get the element at the specified position (also traverse the output)	
    	for(int i = 0;i<list.size();i++){
    		System.out.println(list.get(i));
    	}
    	//You can also use enhanced for
    	for (String string : list) {
			System.out.println(string);
		}  	
	}
}

Implementation class of List

As an interface, the implementation class of List is used when we create objects (the ArrayList implementation class is used in the above code example). In the List interface, there are three common implementation classes: ArrayList, Vector and LinkedList. The following analyzes and introduces them from the source code.

ArrayList

The bottom layer of ArrayList is realized through array. ArrayList can be dynamically expanded with the increase of elements. It is an array queue, which is the most used class in the Java collection framework, but it is thread unsafe.

  • Features: it is stored in the form of array, so the random access speed is fast, so it is suitable for query
  • Disadvantages: not applicable to insert and delete operations, because each operation needs to move the elements in the array; Thread unsafe

Let's take a look at the source code of ArrayList

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
    /**
     * Default initial capacity. If no length is specified during initialization, the default length of 10 is used
     */
    private static final int DEFAULT_CAPACITY = 10;
   /**
     * Shared empty array instance used for empty instances. Empty array
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};
  /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.  Empty array
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
     * Constructs an empty list with an initial capacity of ten.Construct an initial capacity of 10
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//Initialize to empty array
    }


    public boolean add(E e) {
        //Check whether the current array has enough more than one element
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        
        //Save the new element to the [size] position, and then increase the size by 1
        elementData[size++] = e;
        return true;
    }
   
   private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

   private static int calculateCapacity(Object[] elementData, int minCapacity) {
  		 //If the current array is still empty
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //Then minCapacity takes default_ Maximum value of capacity and minCapacity
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //Check whether capacity expansion is required
        return minCapacity;
      }
    
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//Number of modifications plus 1

        // If the required minimum capacity is larger than the length of the current array, that is, the current array is not enough, expand the capacity
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;//Current array capacity
        int newCapacity = oldCapacity + (oldCapacity >> 1);//The capacity of the new array is 1.5 times that of the old array
        //See if 1.5 times of the old array is enough
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //See if the 1.5 times of the old array exceeds the maximum array limit
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        
        //Copy a new array
        elementData = Arrays.copyOf(elementData, newCapacity);
  	}
  	public boolean remove(Object o) {
        //First find o the subscript in the array of the current ArrayList
        //Discuss whether o is empty or not
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {//null values are compared with = =
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {//Non null values are compared with equals
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
    private void fastRemove(int index) {
        modCount++;//Number of modifications plus 1
        //Number of elements to be moved
        int numMoved = size - index - 1;
        
        //If you need to move elements, use system Arraycopy move element
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        
        //Empty the elementData[size-1] position to allow GC to reclaim space and reduce the number of elements
        elementData[--size] = null; // clear to let GC do its work
    }
    public E remove(int index) {
        rangeCheck(index);//Check whether the index is legal

        modCount++;//Number of modifications plus 1
        
        //Take out the element at [index]. The element at [index] is the element to be deleted, which is used to return the deleted element finally
        E oldValue = elementData(index);
        
		//Number of elements to be moved
        int numMoved = size - index - 1;
        
        //If you need to move elements, use system Arraycopy move element
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //Empty the elementData[size-1] position to allow GC to reclaim space and reduce the number of elements
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
    public E set(int index, E element) {
        rangeCheck(index);//Check whether the index is legal

        //Take out the element at [index]. The element at [index] is the element to be replaced, which is used to return the replaced element finally
        E oldValue = elementData(index);
        //Replace the element at [index] with element
        elementData[index] = element;
        return oldValue;
    }
    public E get(int index) {
        rangeCheck(index);//Whether the index test is legal

        return elementData(index);//Returns the element at the [index] position
    }
     public int indexOf(Object o) {
        //There are two cases: o is empty or not
        if (o == null) {
            //Look from front to back
            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;
    }
    public int lastIndexOf(Object o) {
         //There are two cases: o is empty or not
        if (o == null) {
            //Look back and forward
            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;
    }

From the above source code, we can see:

  • When initializing ArrayList, if we do not specify the length, it will have a default length of 10, which will be increased by 1.5 times each time
  • Then is the source code introduction of some common methods of ArrayList

Vector

The bottom layer of Vector is also realized through array, and the method is basically the same as ArrayList,. But Vector is thread safe This is because it adds the synchronized keyword to ensure thread safety.

  • Advantages: it is stored in the form of array, so the random access speed is fast, so it is suitable for query; Thread safety
  • Disadvantages: not applicable to insert and delete operations, because each operation needs to move the elements in the array

Let's look at the source code of Vector

  /**
     * Constructs an empty vector so that its internal data array
     * has size {@code 10} and its standard capacity increment is
     * zero.
     */
    public Vector() {
        this(10); //Specify an initial capacity of 10
    }
    public Vector(int initialCapacity) {
        this(initialCapacity, 0);//Specify a capacityIncrement increment of 0
    }
    public Vector(int initialCapacity, int capacityIncrement Increment is 0) {
        super();
        //The validity of the initial capacity of the formal parameter initialCapacity is judged
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        //Created an array of type Object []
        this.elementData = new Object[initialCapacity];//The default is 10
        //Increment, the default is 0. If it is 0, it will be increased by 2 times later. If it is not 0, it will be increased by the increment you specify later
        this.capacityIncrement = capacityIncrement;
    }
    //synchronized means thread safe   
	public synchronized boolean add(E e) {
        modCount++;
    	//See if capacity expansion is needed
        ensureCapacityHelper(elementCount + 1);
    	//Store the new element in [elementCount]. After saving, the number of elementCount elements will increase by 1
        elementData[elementCount++] = e;
        return true;
    }

    private void ensureCapacityHelper(int minCapacity) {
        // overflow-conscious code
        //See if the capacity of the current array is exceeded
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);//Capacity expansion
    }
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;//Gets the length of the current array
        //If the capacity increment is 0, the new capacity = 2 times the oldCapacity
        //If the capacityIncrement increment is 0, the new capacity = oldCapacity + capacityIncrement increment;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        
        //If the new capacity calculated above is not enough, expand the minCapacity according to the minimum capacity you specify
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        
        //If the new capacity exceeds the maximum array limit, it is processed separately
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        
        //Copy the data from the old array to the new array. The length of the new array is newCapacity
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
     public boolean remove(Object o) {
        return removeElement(o);
    }
    public synchronized boolean removeElement(Object obj) {
        modCount++;
        //Find the subscript of obj in the current Vector
        int i = indexOf(obj);
        //If I > = 0, it indicates that it exists. Delete the element at [i]
        if (i >= 0) {
            removeElementAt(i);
            return true;
        }
        return false;
    }
    public int indexOf(Object o) {
        return indexOf(o, 0);
    }
    public synchronized int indexOf(Object o, int index) {
        if (o == null) {//The element to find is a null value
            for (int i = index ; i < elementCount ; i++)
                if (elementData[i]==null)//If it is a null value, use = = null to judge
                    return i;
        } else {//The element to find is a non null value
            for (int i = index ; i < elementCount ; i++)
                if (o.equals(elementData[i]))//If it is a non null value, use equals to judge
                    return i;
        }
        return -1;
    }
    public synchronized void removeElementAt(int index) {
        modCount++;
        //Judge the legitimacy of subscript
        if (index >= elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " +
                                                     elementCount);
        }
        else if (index < 0) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
        
        //j is the number of elements to be moved
        int j = elementCount - index - 1;
        //If you need to move elements, call system Arraycopy to move
        if (j > 0) {
            //Move the index+1 position and the following elements forward
            //Move the element at the position of index+1 to the position of index, and so on
            //Move j in total
            System.arraycopy(elementData, index + 1, elementData, index, j);
        }
        //The total number of elements decreased
        elementCount--;
        //Empty the location of elementData[elementCount] to add new elements. The elements in the location are waiting to be recycled by GC
        elementData[elementCount] = null; /* to let gc do its work */
    }

From the above source code, we can see:

  • If we do not specify the length of Vector during initialization, it will have a default length of 10, which will be increased by 2 times each time
  • Then there is the source code introduction of some common methods of Vector

LinkedList

The underlying data storage structure of LinkedList is linked list structure, and it is also a two-way linked list, which can realize two-way operation. In addition, LinkedList also implements the operation methods of stack and queue, so it can also be used as stack, queue and double ended queue, such as peek, push, pop and other methods.

  • Advantages: it is stored in the form of linked list, so the random access speed is slow to query and fast to add and delete.
  • Disadvantages: thread unsafe

Let's take a look at the source code

int size = 0;
Node<E> first;//Record the position of the first node
Node<E> last;//Record the position of the last node

    private static class Node<E> {
        E item;//Element data
        Node<E> next;//Next node
        Node<E> prev;//Previous node

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
      public boolean add(E e) {
        linkLast(e);//By default, the new element is linked to the end of the linked list
        return true;
    }
    void linkLast(E e) {
        final Node<E> l = last;//Record the original last node with l
        
        //Create a new node
        final Node<E> newNode = new Node<>(l, e, null);
        //Now the new node is the last node
        last = newNode;
        
        //If l==null, the original linked list is empty
        if (l == null)
            //Then the new node is also the first node
            first = newNode;
        else
            //Otherwise, link the new node to the next of the original last node
            l.next = newNode;
        //Increase in the number of elements
        size++;
        //Increase in modification times
        modCount++;
    }
     public boolean remove(Object o) {
        //There are two situations: whether o is empty or not
        if (o == null) {
            //Find the node x corresponding to o
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);//Delete x node
                    return true;
                }
            }
        } else {
            //Find the node x corresponding to o
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);//Delete x node
                    return true;
                }
            }
        }
        return false;
    }
    E unlink(Node<E> x) {//x is the node to be deleted
        // assert x != null;
        final E element = x.item;//Data of deleted node
        final Node<E> next = x.next;//The next node of the deleted node
        final Node<E> prev = x.prev;//The previous node of the deleted node

        //If the previous node is not deleted, it indicates that the first node is deleted
        if (prev == null) {
            //Then the next node of the deleted node becomes the first node
            first = next;
        } else {//The deleted node is not the first node
            //The next of the previous node of the deleted node points to the next node of the deleted node
            prev.next = next;
            //Disconnect the link between the deleted node and the previous node
            x.prev = null;//Make GC recycle
        }

        //If there is no node after the deleted node, it means that the deleted node is the last node
        if (next == null) {
            //Then the previous node of the deleted node becomes the last node
            last = prev;
        } else {//The deleted node is not the last node
            //The prev of the next node of the deleted node executes the previous node of the deleted node
            next.prev = prev;
            //Disconnect the deleted node from the next node
            x.next = null;//Make GC recycle
        }
		//The data of the deleted node is also set to be empty to make GC recycle
        x.item = null;
        //Decrease in the number of elements
        size--;
        //Increase in modification times
        modCount++;
        //Returns the data of the deleted node
        return element;
    }

From the above source code, we can see:

  • LinkedList is based on linked list, so there is no expansion method. By default, the element is automatically expanded at the end
  • Then is the source code introduction of some common methods of LinkedList

The difference between ArrayList and Vector

Their underlying structures are arrays, which we call dynamic arrays.

  • ArrayList is a new version of dynamic array, which is thread unsafe and efficient. Vector is an old version of dynamic array, which is thread safe and inefficient.
  • The capacity expansion mechanism of dynamic array is different. The capacity expansion of ArrayList is 1.5 times that of the original, and the capacity expansion of Vector is 2 times that of the original.
  • The initialization capacity of the array. If the initialization capacity is not explicitly specified when building the collection object of ArrayList and Vector, the initial capacity of the internal array of Vector is 10 by default, while ArrayList is in jdk1 6 and earlier versions are also 10, while jdk1 In versions after 7, ArrayList is initialized to an empty array with a length of 0, and then an array with a length of 10 is created when the first element is added.

Topics: Java list