LinkedList source code analysis -- Based on JDK 1.8

UML diagram of LinkedList:

LinkedList is really used to store the data structure of elements - > node class
Node class is a private internal class in LinkedList, which stores the elements in the collection through node

  • E: Value of node
  • Node next: reference to the next node of the current node (it can be understood as a pointer to the next node of the current node)
  • Node prev: reference to the previous node of the current node (it can be understood as a pointer to the previous node of the current node)

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

Elements of LinkedList

//Number of LinkedList nodes, used to record the size of LinkedList
transient int size = 0;

/**
 * Pointer to the first node. The header node used to represent LinkedList
 * Invariant: (first == null && last == null) ||
 *            (first.prev == null && first.item != null)
 */
transient Node<E> first;

/**
 * Pointer to the last node. Tail node used to represent LinkedList
 * Invariant: (first == null && last == null) ||
 *            (last.next == null && last.item != null)
 */
transient Node<E> last;

Constructor for LinkedList (two)

There are three constructors for ArrayList, which provide an additional initialization capacity than LinkedList to initialize classes. LinkedList does not provide this method, because the underlying LinkedList is implemented through linked lists. When adding new elements, it is implemented by linking new nodes, that is to say, its capacity changes dynamically with the number of elements. The bottom layer of ArrayList stores the newly added elements through array, so we can set the initial capacity (that is, set the size of array) for ArrayList

/**
 * Space parameter structure
 */
public LinkedList() {
}

/**
 * Constructs a list containing the specified collection elements in the order in which they are returned by the iterator of the collection
 * A combination is passed in to initialize the LinkedList as a parameter.
 *
 * @param  c A collection whose elements are placed in this list
 * @throws NullPointerException If the specified collection is empty
 */
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

Constructor for LinkedList with arguments

There is an important method addAll(int index, Collection) in the LinkedList constructor with collection parameters. This method is a bit convoluted. If you don't understand it, it's recommended to draw it by hand.

/**
 * Add a collection by calling addall (int index, collection <? Extensions E > C)
 */
public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}
/**
 * checkPositionIndex(index); Check whether the parameter passed in is legal
 */
public boolean addAll(int index, Collection<? extends E> c) {
    checkPositionIndex(index);
    // Convert collection to array
    Object[] a = c.toArray();
    // Length of numNew storage array
    int numNew = a.length;
    // If c: the collection to be added is null, return false directly, and do not perform the following operations
    if (numNew == 0)
        return false;
    // pred: refers to the previous node to be added
    // succ: refers to the location of the node to be added
    Node<E> pred, succ;
     // If index==size, it means that every element in the collection that needs to add LinkedList is at the end of LinkedList. So set succ to null and PRED to the tail node
     // Otherwise, the succ points to the node to be inserted. The node(index) method is used here. pred points to the previous node of the succ node
    if (index == size) {
        // The location of the newly added element is after the last element of LinkedList, that is, append the element at the end of LinkedList
        succ = null;
        pred = last;
    } else {
        succ = node(index);
        pred = succ.prev;
    }
    /**
      * Iterates through each element of the array. In each traversal, a new node is created. The value of this node stores the traversal value in array A. The prev of this node stores the pred node. next is set to null.
      * Then judge whether the previous node of the node is empty. If it is empty, set the current node as the head node
      * Otherwise, the next value of the previous node of the current node is set to the current node. Finally, pred is pointed to the current node to facilitate the addition of subsequent nodes
      */
    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o;
        Node<E> newNode = new Node<>(pred, e, null);
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        pred = newNode;
    }
    /**
      * When succ is null, the newly added node follows the last element of the LinkedList collection.
      * All the elements of a traversed by for above. At this time, pred points to the last element in Linked, so last points to the node that pred points to
      * When it is not empty, it indicates that the element added in the LinkedList collection needs to point the next of the pred to the succ, and the prev of the succ to the pred
      */
    if (succ == null) {
        last = pred;
    } else {
        pred.next = succ;
        succ.prev = pred;
    }
    // Resize collection
    size += numNew;
    // Number of changes - Auto increment.
    modCount++;
    return true;
}

Some auxiliary methods in LinkedList

  • linkFirst(E e) {}; takes the element in the parameter as the first element of the list
/**
 * Link e as the first element.
 */
private void linkFirst(E e) {
    final Node<E> f = first;// The first element in LinkedList is assigned to f
    // Build a new node, and the newly added element's succ (after which the pointer points to f)
    final Node<E> newNode = new Node<>(null, e, f);
    // Assign the newly inserted node e to first
    first = newNode;
    // If f== null, the description is an empty linked list, and the new node is set as the tail node
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}
  • linkLast(E e) {}; takes the element in the parameter as the last element of the list
/**
 * Link e as the last element.
 */
void linkLast(E e) {
    // Get tail element
    final Node<E> l = last; 
    // Create a new node with the tail element as the predecessor
    final Node<E> newNode = new Node<>(l, e, null);
    // Update the tail node to the node to be inserted
    last = newNode;

    if (l == null)
        //If it is an empty linked list: update the first node to the node to be inserted at the same time. (that is to say, this node is both the head node and the tail node last)
        first = newNode;
    else
        // If it is not an empty list: point the next of the original tail node (now it is the second node) to the node to be inserted
        l.next = newNode;
    size++;  // Update linked list size and modification times, insert completed
    modCount++;
}
  • Linkbefore (e e e, node succ); insert element E before non empty node succ
/**
 * Insert element e before the non empty node succ.
 */
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    // Create a pred variable to point to the previous node of the succ node
    final Node<E> pred = succ.prev;
    // Create a new node. Its prev is set to our new pred variable, and the post node is set to succ
    final Node<E> newNode = new Node<>(pred, e, succ);
    // The upper node (prev) of succ points to the new node
    succ.prev = newNode;
    // If the former node of succ is null, set the new node as the head node of the linked list
    if (pred == null)
        first = newNode;
    else
        // Not null, the next of the previous node of succ points to the new node
        pred.next = newNode;
    size++;
    modCount++;
}
  • Unlinkfirst (node f) {}; deletes the first node in the LinkedList. (this node is not empty, return the value of the deleted node). This is a private method, we can't call assert f = = first & & F! = null; parameter f is the header node, and F can't be null
/**
 * Unlink the first non empty node f.
 */
private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    // Define a variable element whose value is the value of the node to be deleted.
    final E element = f.item;
    // Define the next variable with the value: the next node of the node to be deleted
    final Node<E> next = f.next;
    // The value of the f node is set to null
    f.item = null;
    f.next = null; // help GC
    // Set the variable next as the header node
    first = next;
    // Judge whether the next node of f is empty. Empty: set last to empty
    if (next == null)
        last = null;
    else
        // Not null, set the previous node of next to null. Next is the head node
        next.prev = null;
    size--;
    modCount++;
    return element;
}
  • unlinkLast(Node l) {}; delete the last node of LinkedList (the node is not empty and returns the corresponding value of the deleted node)
/**
 * Unlinks non-null last node l.
 */
private E unlinkLast(Node<E> l) {
    // assert l == last && l != null;
    // Create the variable element with the value of the node to be deleted
    final E element = l.item;
    // Create variable prev: the value is: the front node of the node to be deleted
    final Node<E> prev = l.prev;
    // Empty the value of the node to be deleted
    l.item = null;
    l.prev = null; // help GC
    // Point last to the newly created node prev 
    last = prev;
    // Judge whether the front node of the node to be deleted is empty. Empty: the list is empty. Assign the first value of the head node to null
    if (prev == null)
        first = null;
    else
        // Not null, point the next of the previous node to be deleted to null
        prev.next = null;
    size--;
    modCount++;
    return element;//Returns the value of the deleted node
}
  • unlink(Node e); delete a node, which is not empty
/**
 * Unlinks non-null node x.
 */
E unlink(Node<E> x) {
    // assert x != null;
    // Variable element: Value: the value of the node to delete
    final E element = x.item;
    // New variable: next, value: the next node of the node to be deleted
    final Node<E> next = x.next;
    // New variable: prev, value: the previous node of the node to be deleted
    final Node<E> prev = x.prev;
    // Judge whether the previous node of the node to be deleted is empty; if it is empty, then the deleted node is the header node, and point first to the new next node
    if (prev == null) {
        first = next;
    } else {
        // Not empty: the next of the previous node of the node to be deleted points to the next node of the node to be deleted
        prev.next = next;
        x.prev = null;
    }
    // Judge whether the next node of the node to be deleted is empty. If it is empty, the last node of the deleted node will point to prev, that is, the last node of the node to be deleted
    if (next == null) {
        last = prev;
    } else {
        // It is not empty. The upper node of the next node of the node to be deleted points to the previous node of the node to be deleted
        next.prev = prev;
        x.next = null;
    }
    // This step assigns a null value to the node to be deleted, which is helpful for gc recovery
    x.item = null;
    size--;
    modCount++;
    return element;
}
  • Node node(int index); calculates the node on the specified index and returns. Here, LinkedList does not traverse from the beginning, but first compares the index closer to the head node or tail node of the list, and then traverses to obtain the corresponding index node
/**
 * Returns the (non empty) node at the specified element index.
 */
Node<E> node(int index) {
    // assert isElementIndex(index);
    
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

The above are some auxiliary methods, which will be used in the add, get,remove and other methods of LinkedList.

contains(Object o) in LinkedList;

public boolean contains(Object o) {
    return indexOf(o) != -1;
}

public int indexOf(Object o) {
    int index = 0;
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;
}

There are two cases in indexOf(Object o) method,

  1. First, judge whether the parameter o passed in is empty,

    1. Empty: for loop to find. If the value of the item of the first node = = null, the corresponding subscript will be returned if found, otherwise - 1 will be returned
    2. If it is not empty, the for loop searches. If the item value of the first node is equal to o, the corresponding subscript is found and returned, but - 1 is not returned;

I think it's easy to understand the above methods.

Tags: Java

Posted on Thu, 18 Jun 2020 02:27:41 -0400 by aebstract