Java Common Collection Source Analysis 1:List

Posted by sstangle73 on Sun, 06 Mar 2022 18:56:27 +0100

Classification of sets

First, let's look at the overall classification of sets.
Both Collection and Set Chinese can be translated into collections. However, from a Java programming perspective, Collections should be translated into containers and Sets into collections.
Both Collection and Set Chinese can be translated into collections. However, from a Java programming perspective, Collections should be translated into containers and Sets into collections.

ListSet
Sequentialorderdisorder
RepeatabilityRepeatableNot Repeatable
IndexesBy manipulating elements through an index, you can traverse them using a normal for loopWithout an index, you cannot use a normal for loop to traverse.

1.ArrayList

Basic principles, advantages and disadvantages

The length of the array is fixed, for example, 100. When the number of elements exceeds 100, it expands and copies the previous array into a new one.

The drawbacks are:

  1. Array expansion + element copy will be slower, so don't insert data frequently.
  2. If you add an element to the middle of an array, all the elements behind the new element need to be moved one bit backwards, which is not good performance.

The advantages are:

  1. Because array-based implementations allow you to locate an element by its memory address, performance is high when randomly obtaining an element in the array.

Scenarios applicable:

  1. The elements of the ArrayList are arranged in the order in which they are inserted.
  2. Suitable for scenes with low insertion frequency.

Source Code Analysis

Constructor

The default constructor makes the internal array a default empty array.

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

The default initialization array size is 10, but it is not assigned at initialization time. See more List Array expansion is assigned in the first add.

The default value is too small, so constructing an ArrayList typically gives you a size, such as 100 data, to avoid arrays being too small.

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);
    }
}

Source code for add() method

Causes an array element to move.

Each time you add data to the ArrayList, you will determine that the elements of the current array are full.

If it is full, it expands the array and copies the elements from the old array to the new one.

public boolean add(E e) {
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
}

Source code for set() method

public E set(int index, E element) {
    // Check if it is out of bounds
    rangeCheck(index);

    // Get the original value
    E oldValue = elementData(index);
    // Set New Value
    elementData[index] = element;
    // Return the original value
    return oldValue;
}


private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}


transient Object[] elementData;

@SuppressWarnings("unchecked")
E elementData(int index) {
    // Get the original value of position i, Lisi
    return (E) elementData[index];
}

Source code for add(index, element) method

public void add(int index, E element) {
    // Judgement beyond bounds
    rangeCheckForAdd(index);

    // Make sure the array adds this element, directly + 1
    ensureCapacityInternal(size + 1);
    // Make a copy of the data, see the notes
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // The above is equivalent to moving the data one space backwards, and the current index is assigned 
    elementData[index] = element;
    size++;
}

private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);

// Copy elementData from first to second, a total of two elements
// System.arraycopy(elementData, 1, elementData, 2, 2);

Source code for get() method

The advantage is to take the elements out of the array directly.

public E get(int index) {
    // Check array bounds
    rangeCheck(index);

    // Return directly
    return elementData(index);
}

Source code for remove() method

Causes an array element to move.

public class ArrayList<E> {
    public E remove(int index) {
        // Check for out-of-bounds
        rangeCheck(index);
    
        // Number of list length changes: add, delete
        modCount++;
        E oldValue = elementData(index);
        
        int numMoved = size - index - 1;
        if (numMoved > 0)
            // Equivalent to moving all elements forward by one place at index+1
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // The last one is set to null to allow garbage collection to recycle 
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
}

public abstract class AbstractList<E> {
    protected transient int modCount = 0;
}

Array expansion and element copy

By default, the first add() assigns 10 to the array by default.

Enter from the add() method.

Assuming that an array is 10 in size and has 10 elements added, the size of the array is 10 and the capacity is 10.

At this point, the add() method is called to insert an element, and the eleventh element cannot be inserted.

The default is to expand by 1.5 times.

transient Object[] elementData;

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

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


private static final int DEFAULT_CAPACITY = 10;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// elementData has been populated with 10 elements, minCapacity = 11
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    // If it is an empty array
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // Compare to default 10
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

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

    // Indicates to expand the array
    // elementData.length defaults to 10
    if (minCapacity - elementData.length > 0)
        // Expansion
        grow(minCapacity);
}

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    // oldCapacity >> 1 = oldCapacity / 2 = 5
    // newCapacity = 15
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    
    // Or too small
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // The new array is too large
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        // Get a very large value
        newCapacity = hugeCapacity(minCapacity);
    // Finish copying the old array to the new one
    elementData = Arrays.copyOf(elementData, newCapacity);
}

2.LinkedList

Basic principles, advantages and disadvantages

LinkedList, the bottom level is based on a two-way linked list data structure.

Advantage:

  1. A lot of insertions don't need to be scaled up like ArrayList, just keep hanging new nodes onto the list. Therefore, it is suitable for frequent insert and delete operations.
  2. LinkedList can be used as a queue, first in, first out, crashing in an element at the end of the list and pulling out an element from the beginning.

Disadvantages:

  1. It is not appropriate to get an element because you need to traverse the entire list until you find the element with index = 10.

Principle of inserting elements

add()

Insert an element at the end of the two-way list.

public boolean add(E e) {
    linkLast(e);
    return true;
}

/**
 * Links e as last element.
 */
void linkLast(E e) {
    // Original Tail Node
    final Node<E> l = last;
    // Encapsulating a new node specifically represents the following class
    final Node<E> newNode = new Node<>(l, e, null);
    // Cover
    last = newNode;
    // Indicates an empty List
    if (l == null)
        // Re-assign Head Node
        first = newNode;
    else
        // Change the next point of the original l
        l.next = newNode;
    size++;
    // list length is variable
    modCount++;
}

private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

add(index, element)

Inserts an element in the middle of the queue.

public void add(int index, E element) {
    // Guarantee >=0 <=size, just click in, it's easy
    checkPositionIndex(index);

    // Insert at the end of the queue
    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}

// Get node at index location
Node<E> node(int index) {
    // Insert position in the first half of the list
    if (index < (size >> 1)) {
        // Traverse from the beginning to get the node at the index position
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } 
    // Insert position in the second half of the queue
    else {
        // Traverse backwards from the tail to get the node at the index position
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    // Node at index position, the previous Node needs to point to a node with value e
    final Node<E> pred = succ.prev;
    // New Nodes
    final Node<E> newNode = new Node<>(pred, e, succ);
    // Point again, as above
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}

addFirst()

Insert an element at the head of the queue.

public void addFirst(E e) {
    linkFirst(e);
}

private void linkFirst(E e) {
    // Get Head Nodes
    final Node<E> f = first;
    // Create a new header node
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    if (f == null)
        last = newNode;
    else
        // Original Head Node, Pointing to New Head Node
        f.prev = newNode;
    size++;
    modCount++;
}

private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

addLast()

Like the add() method, an element is inserted at the end.

public void addLast(E e) {
    linkLast(e);
}

How to get elements

poll(), queued from the top of the queue

peek(), gets the element at the head of the queue, but the element at the head is not in the queue

getFirst()

getFirst() == peek()

Gets the element of the head, returns the first pointer directly to the data in the ode.

public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

transient Node<E> first;

getLast()

Gets the tail element.

public E getLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return l.item;
}

get(int index)

This method has poor performance and uses the node(index) method () to traverse the list of chains.

public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

// Get node at index location
Node<E> node(int index) {
    // Insert position in the first half of the list
    if (index < (size >> 1)) {
        // Traverse from the beginning to get the node at the index position
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } 
    // Insert position in the second half of the queue
    else {
        // Traverse backwards from the tail to get the node at the index position
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

Principle of deleting elements

removeLast()

public E removeLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}

private E unlinkLast(Node<E> l) {
    // assert l == last && l != null;
    final E element = l.item;
    final Node<E> prev = l.prev;
    
    // Auto garbage collection for jvm
    l.item = null;
    l.prev = null; // help GC
    // Re-pointing the list of chains
    last = prev;
    
    // There was originally only one element
    if (prev == null)
        first = null;
    else
        prev.next = null;
    size--;
    modCount++;
    return element;
}

removeFirst()

removeFirst() == poll()
public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    final E element = f.item;
    final Node<E> next = f.next;
    
    // Re-pointing of Two-way Chain List
    f.item = null;
    f.next = null; // help GC
    first = next;
    
    if (next == null)
        last = null;
    else
        next.prev = null;
    size--;
    modCount++;
    return element;
}

remove(int index)

public E remove(int index) {
    // Not out of bounds
    checkElementIndex(index);
    return unlink(node(index));
}


E unlink(Node<E> x) {
    // assert x != null;
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;

    if (prev == null) {
        first = next;
    } else {
        // Pointing to Front Node
        prev.next = next;
        // To delete the forward and backward pointing of the node itself, also point to null, allowing garbage collection to go away
        x.prev = null;
    }

    if (next == null) {
        last = prev;
    } else {
        // Pointing to the back node
        next.prev = prev;
        x.next = null;
    }

    x.item = null;
    size--;
    modCount++;
    return element;
}

notes

You may have taken a note and looked for many articles to solve a problem. If there are sources that you have not posted, please inform us that the level is limited. If there are errors, you are welcome to comment.

Topics: Java list