1, Overview
Let's take a look at this comment in the source code. Let's try to extract some information from it:
/** * Doubly-linked list implementation of the {@code List} and {@code Deque} * interfaces. Implements all optional list operations, and permits all * elements (including {@code null}). * * <p>All of the operations perform as could be expected for a doubly-linked * list. Operations that index into the list will traverse the list from * the beginning or the end, whichever is closer to the specified index. * * <p><strong>Note that this implementation is not synchronized.</strong> * If multiple threads access a linked list concurrently, and at least * one of the threads modifies the list structurally, it <i>must</i> be * synchronized externally. (A structural modification is any operation * that adds or deletes one or more elements; merely setting the value of * an element is not a structural modification.) This is typically * accomplished by synchronizing on some object that naturally * encapsulates the list. */
- From this comment, we can know that LinkedList is implemented through a two-way linked list, which allows the insertion of all elements, including null. At the same time, it is asynchronous.
[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-7rlinlzx-163608740213) (images / 2. Implementation principle of LinkedList / image-20211101103951991.png)]
In addition to the data field, each node of the two-way linked list also has a front pointer and a rear pointer, which point to the predecessor node and the successor node respectively (if there is a predecessor / successor). In addition, the two-way linked list also has a first pointer to the head node and a last pointer to the tail node.
2, Attributes
// Number of nodes in the linked list transient int size = 0; // Pointer to header node transient Node<E> first; // Pointer to tail node transient Node<E> last;
3, Method
Node is a static internal class defined in LinkedList. It represents the structure of each node in the linked list, including a data field item, a post pointer next and a pre pointer prev.
1. Node structure
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; } }
2. Add element
For the linked list data structure, the operation of adding elements is nothing more than inserting elements at the header / footer, or inserting elements at a specified position. Because the LinkedList has a head pointer and a tail pointer, it only takes O(1) to insert elements into the header or tail of the table, while it needs to traverse the linked list before inserting elements at the specified position, so the complexity is O(n).
- The process of adding elements to the header is as follows:
[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-tjybgnyo-163608740214) (images / 2. Implementation principle of LinkedList / image-20211101104252005.png)]
When inserting a node into the header, it is obvious that the precursor of the current node must be null, and the subsequent node is the node pointed to by the first pointer. Of course, it is necessary to modify the first pointer to point to the new header node. In addition, the original head node becomes the second node, so you need to modify the precursor pointer of the original head node to point to the header node
private void linkFirst(E e) { final Node<E> f = first; // The predecessor of the current node points to null, and the successor pointer to the original header node final Node<E> newNode = new Node<>(null, e, f); // The header pointer points to the new header node first = newNode; // If the original node has a head, update the precursor pointer of the original node; otherwise, update the tail pointer if (f == null) last = newNode; else f.prev = newNode; size++; modCount++; }
- Adding elements at the end of the table is similar to adding elements at the header, as shown in the figure:
[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-fmxtahke-163608740216) (images / 2. Implementation principle of LinkedList / image-20211101104808228.png)]
When inserting a node into the footer, it is obvious that the successor of the current node must be null, and the predecessor node is the node pointed to by the last pointer, and then modify the last pointer to point to the new footer node. In addition, the successor pointer of the original tail node should be modified to point to the new tail node
void linkLast(E e) { final Node<E> l = last; // The predecessor of the current node points to the tail node, and the successor points to null final Node<E> newNode = new Node<>(l, e, null); // The tail pointer points to the new tail node last = newNode; // If there is a tail node, the subsequent pointer of the original node is updated; otherwise, the header pointer is updated if (l == null) first = newNode; else l.next = newNode; size++; modCount++; }
- Finally, insert before specifying the node, as shown in the figure:
[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-7x23h6cx-163608740218) (images / 2. Implementation principle of LinkedList / image-20211101104943855.png)]
When a node is inserted before the specified node, the successor of the current node is the specified node, and the predecessor node is the predecessor node of the specified node. In addition, it is also necessary to modify the successor of the predecessor node as the current node and the predecessor of the successor node as the current node
void linkBefore(E e, Node<E> succ) { // assert succ != null; // Specifies the precursor of the node final Node<E> pred = succ.prev; // The precursor of the current node is the precursor of the pointing node, and the successor is the specified node final Node<E> newNode = new Node<>(pred, e, succ); // Updates the precursor of the specified node to the current node succ.prev = newNode; // Update the successor of the predecessor node if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++; }
3. Delete element
The deletion operation is similar to the addition operation. For example, the process of deleting a specified node is shown in the following figure. The successor of the predecessor node of the current node needs to be modified to the successor of the current node, and the predecessor of the successor node of the current node needs to be modified to the predecessor of the current node (is it easy?):
[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-ovfd7uvu-163608740221) (images / 2. Implementation principle of LinkedList / image-20211101105140691.png)]
Deleting the head node and the tail node is very similar to deleting the specified node. I won't introduce them one by one. The source code is as follows:
// Delete the header node and return the value of the header element private E unlinkFirst(Node<E> f) { // assert f == first && f != null; final E element = f.item; final Node<E> next = f.next; f.item = null; f.next = null; // help GC first = next; // The header pointer points to the next node if (next == null) last = null; else next.prev = null;// The precursor of the new header node is null size--; modCount++; return element; } // Delete the footer node and return the value of the footer element private E unlinkLast(Node<E> l) { // assert l == last && l != null; final E element = l.item; final Node<E> prev = l.prev; l.item = null; l.prev = null; // help GC last = prev;// The tail pointer points to the previous node if (prev == null) first = null; else prev.next = null;// The successor of the new tail node is null size--; modCount++; return element; } // Deletes the specified node and returns the value of the specified element E unlink(Node<E> x) { // assert x != null; final E element = x.item; final Node<E> next = x.next;// Successor of current node final Node<E> prev = x.prev;// Precursor of current node if (prev == null) { first = next; } else { prev.next = next;// Update the successor of the predecessor node to the successor of the current node x.prev = null; } if (next == null) { last = prev; } else { next.prev = prev;// Update the predecessor of the subsequent node to the predecessor of the current node x.next = null; } x.item = null; size--; modCount++; return element; }
4. Get element
// Get header element public E getFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return f.item; } // Get footer element public E getLast() { final Node<E> l = last; if (l == null) throw new NoSuchElementException(); return l.item; } // Gets the element of the specified subscript Node<E> node(int index) { // assert isElementIndex(index); // According to whether the subscript exceeds half the length of the linked list, you can choose whether to traverse from the head or from the tail 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; } }
5. Common methods
The addition and deletion of linked lists were introduced earlier. You will find that those methods are not public. LinkedList operates on these basic methods. Let's see what methods we can call.
// Delete header element public E removeFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); } // Delete footer element public E removeLast() { final Node<E> l = last; if (l == null) throw new NoSuchElementException(); return unlinkLast(l); } // Insert new header node public void addFirst(E e) { linkFirst(e); } // /Insert new footer node public void addLast(E e) { linkLast(e); } // Size of linked list public int size() { return size; } // Add element to footer public boolean add(E e) { linkLast(e); return true; } // Deletes the specified element public boolean remove(Object o) { if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) { unlink(x); return true; } } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) { unlink(x); return true; } } } return false; } // Gets the element of the specified subscript public E get(int index) { checkElementIndex(index); return node(index).item; } // Replaces the value of the specified subscript public E set(int index, E element) { checkElementIndex(index); Node<E> x = node(index); E oldVal = x.item; x.item = element; return oldVal; } // Inserts a node at the specified location public void add(int index, E element) { checkPositionIndex(index); if (index == size) linkLast(element); else linkBefore(element, node(index)); } // Deletes the node with the specified subscript public E remove(int index) { checkElementIndex(index); return unlink(node(index)); } // Get the value of the header node. If the header is empty, null will be returned public E peek() { final Node<E> f = first; return (f == null) ? null : f.item; } // Get the value of the header node. If the header is empty, an exception will be thrown public E element() { return getFirst(); } // Get the value of the header node and delete the header node. If the header is empty, null will be returned public E poll() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); } // Add element to header public void push(E e) { addFirst(e); } // Delete header element public E pop() { return removeFirst(); }
4, Summary
- The underlying structure of LinkedList is a two-way linked list with head / tail pointers, which can quickly operate the head / tail nodes.
- Compared with array, the characteristic of linked list is that it is more efficient to insert and delete elements at the specified position, but the efficiency of search is not as high as array.