LinkedList source code analysis

Posted by txhoyt on Tue, 17 Dec 2019 09:57:45 +0100

I. overview

This paper is based on JDK8

The underlying LinkedList is realized by the data structure of two-way collection

  • Memory does not need continuous space guarantee
  • Element search can only be sequential traversal search
  • Better performance for add and delete operations

LinkedList can be used as List, queue and stack. It supports adding and deleting operations from the head, middle and tail of the collection.

The inheritance and implementation diagram of LinkedList is shown below.

<div align = "center"> <img src="https://img-blog.csdnimg.cn/20191209175140775.png" /> </div>

The following instructions are taken from the JDK documentation.

  • Iteratable interface: provides iterator access, which allows objects to traverse through for each loop statement.
  • Collection interface: the root interface in the collection hierarchy. A Set of objects in a collection is called an element. Some collections allow duplicate elements, while others do not. Some are orderly, while others are disordered. JDK does not provide any direct implementation of this interface, it provides more implementation of specific sub interfaces, such as Set and List. This interface is often used to pass collections and where maximum versatility is required.
  • AbstractCollection abstract class: this class provides a basic implementation of the Collection interface to minimize the work required to implement this interface.
  • List interface: a sub interface of the Collection interface, an ordered Collection (also known as a sequence). Through this interface, users can precisely control the insertion position of each element in the list. Users can access elements through their integer indexes (locations in the Collection) and search for them.
  • AbstractList abstract class: this class provides a basic implementation of the List interface to minimize the work required to implement this interface by a "random access" data store, such as an array.
  • AbstractSequentialList abstract class: this class provides a basic implementation of the List interface to minimize the work required to implement this interface by "sequential access" datastores, such as collections. For random access data, such as arrays, the AbstractList class should take precedence.
  • Queue interface: a Collection designed to hold elements before processing. In addition to basic Collection interface operations, queues provide other insert, extract, and check operations. These methods all exist in two forms: one that throws an exception when the operation fails, and the other that returns a special value (depending on the operation, null or false). The latter form of the insert operation is designed for a queue implementation with limited capacity; in most implementations, the insert operation does not fail.
  • Deque interface: supports linear sets of elements to be inserted and deleted at both ends. The name deque is an abbreviation for "double ended queue", usually pronounced "deck". Most deque implementations have no fixed limit on the number of elements that an element may contain, but this interface supports two-end queues with limited capacity and two-end queues without a fixed size limit.
  • Clonable interface: this tag interface provides the ability of instance cloning.
  • Serializable interface: this tag interface provides the ability to serialize or deserialize a class.

Source code analysis

2.1 Node class

Node is the private internal class of LinkedList, the core of LinkedList, and the class used to store nodes in LinkedList. E symbol is generic, attribute item is the current element, next is the next node pointing to the current node, prev is the previous node pointing to the current node, which is a double set structure.

/**
 * Node Inner class 
 * @param <E>
 */
private static class Node<E> {
    /** Currently stored elements */
    E item;
    /** Point to the next node of the current node */
    Node<E> next;
    /** Point to the previous node of the current node */
    Node<E> prev;
	
    /**
     * Pass in the previous node, the current element, and the next node for initialization.
     */
    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

2.2 properties

/**
 * serial number
 */
private static final long serialVersionUID = 876323262645176354L;

/**
 * Length of set
 */
transient int size = 0;

/**
 * Collection header node.
 * Invariant: (first == null && last == null) || (first.prev == null && first.item != null)
 */
transient Node<E> first;

/**
 * Set tail node
 * Invariant: (first == null && last == null) || (last.next == null && last.item != null)
 */
transient Node<E> last;

2.3 construction method

2.3.1 LinkedList()

No parameter constructor, construct an empty set.

/**
 * Construct an empty set
 */
public LinkedList() {
}

2.3.2 LinkedList(Collection<? extends E> c)

Constructs a collection containing the specified collection elements, the order of which is returned by the iterator of the collection.

/**
 * Constructs a collection containing the specified collection elements, the order of which is returned by the iterator of the collection.
 *
 * @param  c Specified collection
 * @throws NullPointerException Set is empty throw
 */
public LinkedList(Collection<? extends E> c) {
	// Call parameterless constructor for initialization
    this();
    // Add set c to the set
    addAll(c);
}

2.4 main methods

2.4.1 add(E e)

Add the specified element to the end of the collection. The function of this method is the same as that of the addLast method. It mainly inserts the element to the end through the linkLast method, as shown in the figure.

<div align = "center"> <img src="https://img-blog.csdnimg.cn/2019120917521872.png" /> </div>

/**
 * Adds the specified element to the end of the collection
 * This method has the same function as addLast
 *
 * @param e Added elements
 * @return Return to add successfully
  */
public boolean add(E e) {
    // Call linkLast method to insert element
    linkLast(e);
    return true;
}

/**
 * Adds the specified element to the end of the collection
 * This method is the same as the add method 
 */
public void addLast(E e) {
    linkLast(e);
}

/**
 * Add the element to the end of the collection
 */
void linkLast(E e) {
    // Get old tail node
    final Node<E> l = last;
    // Build a new node, the previous node of the new node points to the old tail node, and the next node is null
    final Node<E> newNode = new Node<>(l, e, null);
    // Update new node to tail node
    last = newNode;
    
    // If the old tail node is empty, the new node is both the head node and the tail node
    if (l == null)
        first = newNode;
    else
        // If the old tail node is not empty, point the next node of the old tail node to the new tail node
        l.next = newNode;
    // Set length + 1
    size++;
    // Number of changes + 1 for fail fast iterators
    modCount++;
}

2.4.2 addFirst(E e)

Add this element to the header of the collection, mainly by calling the linkFirst method, as shown in the figure.

<div align = "center"> <img src="https://img-blog.csdnimg.cn/20191209175316948.png" /> </div>

/**
 * Add this element to the head of the collection
 *
 * @param e Added elements
 */
public void addFirst(E e) {
    linkFirst(e);
}

/**
 * Add this element to the head of the collection
 */
private void linkFirst(E e) {
    // Get collection old head node
    final Node<E> f = first;
    // Build a new node, the next node of the new node is the old first node, and the previous node is null
    final Node<E> newNode = new Node<>(null, e, f);
    // Update the new node to the first node
    first = newNode;
    
    // If the old first node is empty, the new node is both the first and the last nodes
    if (f == null)
        last = newNode;
    else
        // Point the previous node of the old head node to the new head node
        f.prev = newNode;
    // Set length + 1
    size++;
    // Modification times + 1
    modCount++;
}


2.4.3 add(int index, E element)

Inserts the specified element into the collection at the specified location. Shifts the current element (if any) at that location and any subsequent elements to the right. The average time complexity of inserting elements in the middle of a set is O(1). In this way, nodes at corresponding positions are found by node(int index) method, and then inserted by linkbefore (e e, node < E > succ) method. The steps of inserting elements in the middle of a set are as shown in the figure.

<div align = "center"> <img src="https://img-blog.csdnimg.cn/20191209175343706.png" /> </div>

/**
 * Inserts the specified element into the collection at the specified location
 *
 * @param index Specified location of insertion
 * @param element Inserted elements
 * @throws IndexOutOfBoundsException Subscript out of range throw
 */
public void add(int index, E element) {
    // Check if index is out of range
    checkPositionIndex(index);

    // If index == size, the linkLast method is called to insert the element into the last
    if (index == size)
        linkLast(element);
    else
        // Insert the node before the node in the original index location
        linkBefore(element, node(index));
}

/**
 * Check whether the subscript is out of range
 *
 * @param index
 */
private void checkPositionIndex(int index) {
    // IndexOutOfBoundsException is thrown when isPositionIndex returns false
    if (!isPositionIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

/**
 * Index that checks if the subscript is a valid location for an iterator or add operation
 */
private boolean isPositionIndex(int index) {
    // If the subscript is less than 0 or greater than size, false will be returned
    return index >= 0 && index <= size;
}

/**
 * Details of building IndexOutOfBoundsException exception
 */
private String outOfBoundsMsg(int index) {
    return "Index: "+index+", Size: "+size;
}

/**
 * Insert element e before non null node succ
 * succ The node is the node where the subscript index is located. Move back the nodes including this node and the nodes behind this node
 */
void linkBefore(E e, Node<E> succ) {
    // Get the previous node of the succ node
    final Node<E> pred = succ.prev;
    // Construct a new node. The previous node of the new node is pred, and the next node is succ
    final Node<E> newNode = new Node<>(pred, e, succ);
    // Point the previous node of succ to the new node
    succ.prev = newNode;
    
    // If the pred node is empty, the inserted new node points to the first node
    if (pred == null)
        first = newNode;
    else
        // If the pred node is not empty, the next node of the pred node points to the new node
        pred.next = newNode;
    // Set length + 1
    size++;
    // Modification times + 1
    modCount++;
}

2.4.4 remove()

Delete the first node of the collection and return the element. Like the removeFirst method, it mainly uses the unlinkFirst method to delete the header node and return the value of the header node. When deleting the node, set the corresponding node value and node direction to null, which is convenient for GC recycling. Delete as shown in the figure.

<div align = "center"> <img src="https://img-blog.csdnimg.cn/201912091754216.png" /> </div>

/**
 * Delete the first node of the collection and return the element
 *
 * @return Return to collection header node
 * @throws NoSuchElementException Set is empty throw
 */
public E remove() {
    return removeFirst();
}

/**
 * Delete the first node of the collection and return the element
 *
 * @return Return to collection header node
 * @throws NoSuchElementException Set is empty throw
 */
public E removeFirst() {
    // Get the first node
    final Node<E> f = first;
    // No first node threw NoSuchElementException
    if (f == null)
        throw new NoSuchElementException();
    // Delete first node
    return unlinkFirst(f);
}

/**
 * Delete non null first node
 */
private E unlinkFirst(Node<E> f) {
    // Get the value of the old first node
    final E element = f.item;
    // Get the next node of the old first node, next is the new first node
    final Node<E> next = f.next;
    // null the value of the old first node and the point of the next node to help GC
    f.item = null;
    f.next = null; 
    // Update the first node with the new first node next
    first = next;
    // If the next node is empty, it means that there is only one node in the original collection, and the tail node also points to null
    if (next == null)
        last = null;
    else
        // If next is not empty, point the previous node of the new first node to null
        next.prev = null;
    // Set length-1
    size--;
    // Modification times + 1
    modCount++;
    // Return the deleted first node element
    return element;
}

2.4.5 removeLast()

Delete the last node of the collection and return the element. Delete the tail node mainly through unlinkLast method and return the value of the tail node. The deletion steps are as shown in the figure.

<div align = "center"> <img src="https://img-blog.csdnimg.cn/20191209175516154.png" /> </div>

/**
 * Delete the tail node and return the element
 *
 * @return Back to tail node
 * @throws NoSuchElementException Set is empty throw
 */
public E removeLast() {
    // Get tail node
    final Node<E> l = last;
    // Throw NoSuchElementException when tail node is null
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}

/**
 * Delete non null tail node
 */
private E unlinkLast(Node<E> l) {
    // Get the value of the old tail node
    final E element = l.item;
    // Get prev of the previous node of the old tail node, prev is the new tail node
    final Node<E> prev = l.prev;
    // Set the value of the old tail node and the point of the next node to null to help GC
    l.item = null;
    l.prev = null;
    // Update tail last with new tail prev
    last = prev;
    // If the new tail node is empty, it means that there is only one node in the collection, and points the first node to null
    if (prev == null)
        first = null;
    else
        // The new tail node is not empty. Point the next node of the new tail node to null
        prev.next = null;
    // Set length-1
    size--;
    // Modification times + 1
    modCount++;
    // Returns the value of the deleted tail node
    return element;
}

2.4.6 remove(int index)

Deletes the element at the specified location in the collection. Move all subsequent elements forward and return the elements deleted from the collection. First obtain the node at the specified location through the node(int index) method, then delete the node through the unlink (node < E > x) method and return the value of the node, as shown in the figure.

<div align = "center"> <img src="https://img-blog.csdnimg.cn/20191209175600858.png" /> </div>

/**
 * Deletes the element at the specified location in the collection. Moves all subsequent elements forward and returns elements removed from the collection
 *
 * @param index Deleted location
 * @return Return the element in the delete location
 * @throws IndexOutOfBoundsException Index out of range throw
 */
public E remove(int index) {
    // Check for out of line
    checkElementIndex(index);
    // Delete the node of the specified subscript
    return unlink(node(index));
}

/**
 * Delete the specified non null node
 */
E unlink(Node<E> x) {
    // Gets the value of the specified node for the last return
    final E element = x.item;
    // Get the next node next of this node
    final Node<E> next = x.next;
    // Get prev of the previous node of this node
    final Node<E> prev = x.prev;

    // If the prev node is null, it indicates that the node is the first node, and points the next node to the first node
    if (prev == null) {
        first = next;
    } else {
        // If prev node is not null, the next node of prev will point to next
        prev.next = next;
        // Set the previous node of this node to null to help GC
        x.prev = null;
    }

    // If the next node is null, it indicates that the node is a tail node, and points the prev node to the tail node
    if (next == null) {
        last = prev;
    } else {
        // Next node is not null, point the previous node of next to prev
        next.prev = prev;
        // Set the next node of this node to null to help GC
        x.next = null;
    }
    // Set the value of this node to null
    x.item = null;
    // Set length-1
    size--;
    // Modification times + 1
    modCount++;
    // Returns the value of the deleted element
    return element;
}

2.4.7 get(int index)

Returns the element at the specified location in the collection. First, check whether the subscript is out of range, and then get the node corresponding to the subscript through the node(index) method. The item attribute of the node is the corresponding value.

/**
 * Returns the element at the specified location in the collection
 *
 * @param index Designated location
 * @return Elements returned
 * @throws IndexOutOfBoundsException Subscript out of range throw
 */
public E get(int index) {
    // Check subscript out of range
    checkElementIndex(index);
    // node(index) return node
    return node(index).item;
}

/**
 * Returns the first element in the collection
 */
public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

/**
 * Returns the last element in the collection
 *
 * @return 
 * @throws NoSuchElementException Set is empty throw exception
 */
public E getLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return l.item;
}

2.4.8 node(int index)

Returns the (non empty) element at the specified element index, which is involved in many methods.

/**
 * Returns the (non empty) element at the specified element index
 */
Node<E> node(int index) {
	
    // Judge whether index is closer to 0 or size to decide which side to traverse
    if (index < (size >> 1)) {
        // Traverse from the first node
        // Get the first node
        Node<E> x = first;
        // Traverse index times from the first node to get the node where index subscript is located
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        // Traverse from tail node
        // Get tail node
        Node<E> x = last;
        // Traverse size-index-1 times from the first node to get the node where the index subscript is located
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

2.4.9 offer(E e)

Add the specified element to the end of the collection. The function of this method is the same as add (e e e) and addlast (e e e). This method is usually preferable to the add method when a two terminal queue with limited capacity is used. When the queue capacity is exceeded, the method returns false, and the add method throws an exception.

/**
 * Adds the specified element to the end of the collection
 * @param e Elements to insert
 * @return Return whether the insertion is successful
 */
public boolean offer(E e) {
    return add(e);
}

/**
 * Inserts a specific element to the end of the collection, just like addLast
 *
 * @param e Inserted elements
 * @return Return true
 */
public boolean offerLast(E e) {
    addLast(e);
    return true;
}

/**
 * Insert an element into the head of the collection
 *
 * @param e Inserted elements
 * @return Return true
 */
public boolean offerFirst(E e) {
    addFirst(e);
    return true;
}

2.4.10 poll()

Delete the header node of the collection. The function of this method is the same as that of remove(). However, when two methods are used for an empty collection, the remove() method throws an exception and the poll() method returns null.

The pollFirst() method does the same thing as the poll() method, returning null when the collection is empty.

The pollLast() method has the same function as the removeLast() method, but when the collection is empty, the pollLast() method returns null and the removeLast() method throws an exception.

/**
 * Delete the collection's head node
 *
 * @return Return null or header node element
 */
public E poll() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}

/**
 * Delete collection header node
 *
 * @return Returns null or an element of a collection
 */
public E pollFirst() {
    // Get the first node
    final Node<E> f = first;
    // Return null if the collection has no elements, otherwise delete the first node
    return (f == null) ? null : unlinkFirst(f);
}

/**
 * Check to delete the last element of the linked list
 *
 * @return Returns null or the last element of the collection
 */
public E pollLast() {
    // Get tail node
    final Node<E> l = last;
    // Return null if the collection has no elements, otherwise delete the tail node
    return (l == null) ? null : unlinkLast(l);
}

2.4.11 peek()

The peek() method does the same thing as the getFirst() method, but when the collection is empty, the peek() method returns null, and the getFirst() method throws an exception.

The function of the peekFirst() method is the same as that of peek(). The function of the peekLast() method is the same as that of the removeLast() method, but the method returns null when it encounters an empty collection.

/**
 * Returns the first element of the collection
 *
 * @return 
 */
public E peek() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}

/**
 * Returns the first element of the collection, just like peek()
 *
 * @return
 */
public E peekFirst() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}

/**
 * Returns the last element of the collection
 *
 * @return 
 */
public E peekLast() {
    final Node<E> l = last;
    return (l == null) ? null : l.item;
}

III. summary

  • The underlying layer of LinkedList is based on linked list. The average time complexity of finding nodes is O(n), and the time complexity of adding and deleting nodes is O(1).
  • LinkedList is suitable for reading less and writing more, and ArrayList is suitable for reading more and writing less.
  • When LinkedList is used as a queue, you can use offer/poll/peek instead of add/remove/get and other methods. These methods will not throw exceptions when encountering empty collection or full queue capacity.

Article synchronization to public numbers and Github , if you have any questions, please contact the author.

<div align = "center"> <img width="300px" src="https://img-blog.csdnimg.cn/20191021125444178.jpg" /> < div > < strong > brilliant life <div> WeChat scan two-dimensional code, pay attention to my public number </div> </div>

Topics: Programming less JDK Attribute github