LinkedList source code reading analysis

LinkedList source code reading analysis
1.1 LinkedList introduction

LinkedList is a two-way linked list inherited from AbstractSequentialList. It can also be operated as a stack, queue, or double ended queue.
LinkedList implements the List interface and can perform queue operations on it.
LinkedList implements the Deque interface, that is, LinkedList can be used as a double ended queue.
LinkedList implements the clonable interface, that is, it overrides the function clone(), which can be cloned.
LinkedList implements the java.io.Serializable interface, which means that LinkedList supports serialization and can be transmitted through serialization.
LinkedList is asynchronous.

1.2 LinkedList source code

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    // Number of elements
    transient int size = 0;

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    // List head node
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    // End of list node
    transient Node<E> last;
.

1.2.1 main construction methods
(1). LinkedList()

public LinkedList() {
}
LinkedList The parameterless construction of is to construct an empty list aggregate

(2). LinkedList(Collection<? extends E> c)

public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

Constructs a list containing the elements of the specified collection, in the order they are returned by the collection's iterator.

1.2.2 main internal categories
Typical double linked list structure.

private static class Node<E> {
    // item represents the current storage element
    E item;
    // next represents the post node of the current node
    Node<E> next;
    // prev represents the front node of the current node
    Node<E> prev;

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

LinkedList is implemented through a two-way linked list, which is implemented through the Node class. The Node class stores the current element through the item variable, points to the next Node of the current Node through the next variable, and points to the previous Node of the current Node through the prev variable.

1.2.3 adding nodes to LinkedList
(1). add(E e)

public boolean add(E e) {
    //Add an element at the end
    linkLast(e);
    return true;
}

void linkLast(E e) {
    // Get the last node of the linked list
    final Node<E> l = last;
    // Create a new node
    final Node<E> newNode = new Node<>(l, e, null);
     // Make the new node the last node
    last = newNode;
     // If the last node is null, it means that the linked list is empty, and the newNode is assigned to the first node
    if (l == null)
        first = newNode;
    // Otherwise, the last of the tail node points to newNode
    else
        l.next = newNode;
    // Number of elements plus 1
    size++;
    // The number of structural modifications increases by + 1
    modCount++;
}

The first step is to obtain the last node of the linked list
The second step is to create a new node
The third step is to make the new node the last node
Step 4: if the last node is null, it means that the linked list is empty, and the newNode is assigned to the first node; Otherwise, the last of the tail node points to newNode
(2). add(int index, E element)
Inserts an element at the specified location

public void add(int index, E element) {
    // Check the location of the index
    checkPositionIndex(index);
        // If index is a position after the end of the queue node
    // Add the new node directly after the tail node
    // Otherwise, the linkBefore() method is called to add nodes in the middle
    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}

private void checkPositionIndex(int index) {
    if (!isPositionIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

private boolean isPositionIndex(int index) {
    return index >= 0 && index <= size;
}

void linkLast(E e) {
    // Get the last node of the linked list
    final Node<E> l = last;
    // Create a new node
    final Node<E> newNode = new Node<>(l, e, null);
     // Make the new node the last node
    last = newNode;
     // If the last node is null, it means that the linked list is empty, and the newNode is assigned to the first node
    if (l == null)
        first = newNode;
    // Otherwise, the last of the tail node points to newNode
    else
        l.next = newNode;
    // Number of elements plus 1
    size++;
    // The number of structural modifications increases by + 1
    modCount++;
}

void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    // succ is the successor node of the node to be added
    // Find the front node of the node to be added
    final Node<E> pred = succ.prev;
    // Create a new node between its predecessor and successor
    final Node<E> newNode = new Node<>(pred, e, succ);
    // Build a two-way linked list, and the precursor node of succ is a new node
    succ.prev = newNode;
    // If the preamble node is null, assign newNode to first
    if (pred == null)
        first = newNode;
    else
        // Building bidirectional lists
        pred.next = newNode;
    // Number of elements plus 1
    size++;
    // The number of structural modifications increases by + 1
    modCount++;
}

// Find node at index location
Node<E> node(int index) {
    // Because it's a double linked list
    // Therefore, whether to traverse from the front or from the back depends on whether the index is in the first half or the second half
    // In this way, the index can traverse half of the elements in the second half
    if (index < (size >> 1)) {
        // If it's in the first half
        // Just traverse the past
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        // If it's in the second half
        // Just traverse from the back
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

(3). addAll(Collection<? extends E> c)

public boolean addAll(Collection<? extends E> c) {
    // Add a collection at the end of the linked list
    return addAll(size, c);
}

(4).addAll(int index, Collection<? extends E> c)
Adds a collection at the specified location

public boolean addAll(int index, Collection<? extends E> c) {
    //Check whether the index is out of bounds
    checkPositionIndex(index);
        // Converts a collection into an array
    Object[] a = c.toArray();
    int numNew = a.length;
    // If the collection is empty, the addition fails and returns false
    if (numNew == 0)
        return false;
   // Declare node < E > type variables pred (element direct predecessor), succ (element direct successor)
    Node<E> pred, succ;
    // If the given subscript is equal to the number of original elements in the linked list
    if (index == size) {
        succ = null; // Sets the direct successor of the given tail collection element to null
        pred = last; // Set the direct precursor of the given set header element to last (original linked list tail element)
    } else {
        succ = node(index); // The tail element of a given set is directly followed by node(index)
        pred = succ.prev;   // The direct precursor of the given set header element is succ.prev (determine the previous linked list element pred)
    }
        // Traverse the collection elements and add them to the linked list in turn
    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o;
        // Add tail element
        Node<E> newNode = new Node<>(pred, e, null);
        // If it is a header element, create the first element
        if (pred == null)
            first = newNode;
        else
                   // If it is not empty, the next element is inserted
            pred.next = newNode;
        //To prepare for the next cycle, set the element's direct precursor to newNode
        pred = newNode;
    }
        If the element is directly followed by null´╝îIndicates that this is a tail element 
    if (succ == null) {
        last = pred; //Because pred = newNode at the end of the last cycle; So assign the last element as the tail element
    } else {

        //Post assignment node
        pred.next = succ;
        //Pre assignment node
        succ.prev = pred;
    }
        // Modify the number of linked list elements
    size += numNew;
    // The number of structural changes increases
    modCount++;
    return true;
}

(5). addFirst(E e)
Add element from queue header

public void addFirst(E e) {
    // Add element from queue header 
    linkFirst(e);
}

private void linkFirst(E e) {
    // First node
    final Node<E> f = first;
    // Create a new node. The next node of the new node is the first node
    final Node<E> newNode = new Node<>(null, e, f);
    // Make the new node the new first node
    first = newNode;
    // Determine whether it is the first element added
    // If so, set last as the new node
    // Otherwise, set the prev pointer of the original first node to the new node
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    // Number of elements plus 1
    size++;
    // The number of structural changes increases
    modCount++;
}

(6). addLast(E e)
Add element from end of queue

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

void linkLast(E e) {
    // Get the last node of the linked list
    final Node<E> l = last;
    // Create a new node
    final Node<E> newNode = new Node<>(l, e, null);
     // Make the new node the last node
    last = newNode;
     // If the last node is null, it means that the linked list is empty, and the newNode is assigned to the first node
    if (l == null)
        first = newNode;
    // Otherwise, the last of the tail node points to newNode
    else
        l.next = newNode;
    // Number of elements plus 1
    size++;
    // The number of structural modifications increases by + 1
    modCount++;
}

1.2.3 deleting LinkedList nodes
(1). remove()
Delete element from team leader

public E remove() {
    return removeFirst();
}

public E removeFirst() {
    final Node<E> f = first;
    // The first node is null and an exception is thrown
    if (f == null)
        throw new NoSuchElementException();
    //Delete node from queue leader
    return unlinkFirst(f);
}

// Delete first node
private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    // Element value of the first node
    final E element = f.item;
    // next pointer of the first node
    final Node<E> next = f.next;
    // Set the first node. The next pointer of the first node is null to assist GC
    f.item = null;
    f.next = null; // help GC
    // Take the next of the first node as the new first node
    first = next;
    // If only one element is deleted, set last to empty
    // Otherwise, set the leading pointer of next to null
    if (next == null)
        last = null;
    else
        next.prev = null;
    // The number of linked list elements decreases by - 1
    size--;
    //Number of structural modifications increased by + 1
    modCount++;
    // Returns the deleted element
    return element;
}

(2). remove(int index)
Deletes the element at the specified index location

public E remove(int index) {
    //Check whether the index is out of bounds
    checkElementIndex(index);
    //Delete node
    return unlink(node(index));
}
// Check index out of bounds
private void checkElementIndex(int index) {
    if (!isElementIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

private boolean isElementIndex(int index) {
    return index >= 0 && index < size;
}


 // Find node at index location
Node<E> node(int index) {
    // assert isElementIndex(index);
        // Therefore, whether to traverse from the front or from the back depends on whether the index is in the first half or the second half
    // In this way, the index can traverse half of the elements in the second half
    if (index < (size >> 1)) {
        // If it's in the first half
        // Just traverse the past
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        // If it's in the second half
        // Just traverse from the back
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

E unlink(Node<E> x) {
    // assert x != null;
    // Element value of x
    final E element = x.item;
    // Front node of x
    final Node<E> next = x.next;
    // Post node of x
    final Node<E> prev = x.prev;
        // If the front node is empty
    // The description is the first node. Let first point to the post node of x
    // Otherwise, modify the post node whose next of the front node is x
    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }

    // If the post node is empty
    // The description is the tail node. Let last point to the front node of x
    // Otherwise, modify the front node whose prev is x
    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }
         // Clear the element value of x to assist GC
    x.item = null;
    // Number of elements minus 1
    size--;
    // Number of structural modifications plus 1
    modCount++;
    // Returns the deleted element
    return element;
}

(3). remove(Object o)
Delete according to element

public boolean remove(Object o) {
    //If the element is null, find the position where the element value in the linked list is null for the first time and delete it
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
         //If the element is not null, find the position where the element value first appears in the linked list and delete it
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

(4). removeFirst()
Delete first node

public E removeFirst() {
    // Judge whether the first node is empty. If it is empty, an exception will be thrown
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}
// Delete first node
private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    // Element value of the first node
    final E element = f.item;
    // next pointer of the first node
    final Node<E> next = f.next;
    // Clear the contents of the first node to assist GC
    f.item = null;
    f.next = null; // help GC
    // Take the next of the first node as the new first node
    first = next;
    // If only one element is deleted, set last to empty
    // Otherwise, set the leading pointer of next to null
    if (next == null)
        last = null;
    else
        next.prev = null;
    // Number of elements minus 1
    size--;
    // Number of structural modifications plus 1
    modCount++;
    // Returns the deleted element
    return element;
}

(5). removeLast()
Delete tail node

public E removeLast() {
    // Judge whether the tail node is empty. If it is empty, an exception will be thrown
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    // Delete tail node
    return unlinkLast(l);
}

private E unlinkLast(Node<E> l) {
    // assert l == last && l != null;
    // Element value of tail node
    final E element = l.item;
    // Last pointer to the tail node
    final Node<E> prev = l.prev;
    //Clear the contents of the tail node to assist the GC
    l.item = null;
    l.prev = null; // help GC
    // Make the front node the new tail node
    last = prev;
    // If there is only one element, delete and set first to null
    // Otherwise, set the next of the preceding node to null
    if (prev == null)
        first = null;
    else
        prev.next = null;
    // Number of elements minus 1
    size--;
    // Number of structural modifications plus 1
    modCount++;
    // Returns the deleted element
    return element;
}

(6). removeFirstOccurrence(Object o)
Deletes the first occurrence of the specified element in the list (when traversing the list from head to tail). If the element is not included in the list, it remains unchanged.

public boolean removeFirstOccurrence(Object o) {
    return remove(o);
}

(7).removeLastOccurrence(Object o)
Deletes the last occurrence of the specified element in this list (when traversing the list from beginning to end). If the list does not contain the element, it remains unchanged.

public boolean removeLastOccurrence(Object o) {
    if (o == null) {
        for (Node<E> x = last; x != null; x = x.prev) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node<E> x = last; x != null; x = x.prev) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

1.2.4 LinkedList modification node
(1) set(int index, E element)
Modify node based on index

public E set(int index, E element) {
    //Check whether the index is out of bounds
    checkElementIndex(index);
    // Find the element to modify according to the index
    Node<E> x = node(index);
    // Remove modified elements
    E oldVal = x.item;
    // Assign a new element
    x.item = element;
    // Returns the element before modification
    return oldVal;
}

1.2.5 LinkedList lookup node
(1). get(int index)
Find nodes by index

public E get(int index) {
    // Check whether the index is out of bounds
    checkElementIndex(index);
    // Find by index
    return node(index).item;
}

// Find node at index location
Node<E> node(int index) {
    // assert isElementIndex(index);
    // Therefore, whether to traverse from the front or from the back depends on whether the index is in the first half or the second half
    // In this way, the index can traverse half of the elements in the second half
    if (index < (size >> 1)) {
        // If it's in the first half
        // Just traverse the past
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        // If it's in the second half
        // Just traverse from the back
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

(2). getFirst()
Get first node

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

(3). getLast()
Get tail node

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

1.3 LinkedList used as stack

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

public E pop() {
    return removeFirst();
}

The stack feature is LIFO(Last In First Out), so it is also very simple to use as a stack. Adding and deleting elements only operate on the first node of the queue.

1.4 LinkedList summary
(1) LinkedList is a List implemented by double linked List;

(2) LinkedList is also a double ended queue, which has the characteristics of queue, double ended queue and stack;

(3) LinkedList is very efficient in adding and deleting elements at the beginning and end of the queue, and the time complexity is O(1);

(4) LinkedList is inefficient to add and delete elements in the middle, and the time complexity is O(n);

(5) LinkedList does not support random access, so it is inefficient to access non queue elements;

(6) LinkedList is functionally equal to ArrayList + ArrayDeque;

Notes:

Array: a linear data structure that uses a continuous set of memory space to store a set of data of the same type.
Linear means that there is no bifurcation. There is only one element before and after any element. Similarly, there are linked lists and queues with linear structure.

Continuous, its storage in the memory space is continuous and uninterrupted. The front and rear elements are close to each other, and there is no gap.

For the same type, the types of elements stored in the array must be the same. Of course, in Java, you can use Object to represent all types. In essence, they are still the same type.

It is the above three characteristics that make the array have the characteristics of random access

Linked list: it is also a thread data structure. Different from array, it is not necessarily stored sequentially in memory space. In order to ensure the continuity of elements in the linked list, a pointer is generally used to find the next element
The linked list does not have the characteristics of random access. In the linked list, you can only find elements from the beginning (single linked list) [double linked list can find elements from the beginning to the end], and its time complexity is O(n).

If you add a precursor pointer (pointer to the previous element) to the single linked list, it becomes a two-way linked list

LinkedList is a typical two-way linked list structure. The two-way linked list can be used as both a queue and a stack, which is very convenient.

Queue: the so-called queue is actually the same as the real queue. The elements enter from one end and go out from the other end. In English, it is called First In, First Out, short for FIFO. [First In First Out]

Stack: stack, which is a data structure completely opposite to the performance of queue. Its elements are advanced and later come out. [in and out]

Tags: Java crawler server

Posted on Wed, 01 Dec 2021 01:10:40 -0500 by crosbystillsnas