LinkedList source code reading analysis

LinkedList source code reading analysis 1.1 LinkedList introduction LinkedList is a two-way linked list inherited from ...

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]

1 December 2021, 01:10 | Views: 1754

Add new comment

For adding a comment, please log in
or create account

0 comments