1, LinkedList overview
1. For frequent operations of inserting or deleting elements, it is recommended to use the LinkedList class, which is more efficient.
2. LinkedList is a double ended linked List that implements the List interface and Deque interface.
3. The underlying linked List structure of LinkedList enables it to support efficient insert and delete operations. In addition, it implements the Deque interface, so that the LinkedList class also has the properties of List operation, double ended queue and stack.
4. LinkedList is not thread safe. If you want to make LinkedList thread safe, you can call the synchronizedList method in the static class Collections class:
List list=Collections.synchronizedList(``new` `LinkedList(...));
5. The inheritance structure of LinkedList is as follows:
2, Implemented interface
Source code:
1 public class LinkedList<E> 2 extends AbstractSequentialList<E> 3 implements List<E>, Deque<E>, Cloneable, java.io.Serializable
(1) The List interface is implemented, with the operation method of List;
(2) Deque interface is implemented, which has the characteristics of double ended queue and stack;
(3) Clonable interface is implemented to support cloning;
(4) The Serializable interface is implemented to support serialization;
3, Internal structure
1. Bidirectional linked list
ArrayList stores data through an array, while LinkedList stores data through a linked list, and it implements a two-way linked list. Briefly, what is a two-way linked list.
Bidirectional linked list is a form of data structure. Each node maintains two pointers. prev points to the previous node and next points to the next node.
What are the characteristics of this structure? It can realize two-way traversal, which makes the data reading in the linked list very flexible and free.
At the same time, LinkedList maintains two pointers, one to the head and one to the tail. After maintaining these two pointers, you can insert elements from the head or from the tail.
Based on this method, users can easily achieve FIFO (queue), LIFO (stack) and other effects. Then let's take a look at the specific implementation in the source code.
2. Internal structure analysis
As shown in the figure:
**
**
LinkedList: a two-way linked list. It does not declare an array internally, but defines the first and last of Node types, which are used to record the first and last elements. At the same time, the internal class Node is defined as the base for saving data in the LinkedList
This structure.
In addition to saving data, Node also defines two variables:
① The prev variable records the position of the previous element
② The next variable records the position of the next element
After reading the figure, let's look at an internal private class Node in the LinkedList class, which is easy to understand:
1 private static class Node<E> { 2 E item; //Value of this node 3 Node<E> next; //Successor node 4 Node<E> prev; //Precursor node 5 6 Node(Node<E> prev, E element, Node<E> next) { 7 this.item = element; 8 this.next = next; 9 this.prev = prev; 10 } 11 }
This class represents the Node of the double ended linked list. This class has three attributes: the precursor Node, the value of this Node and the successor Node.
4, Member variable
In the LinkedList class, there are several member variables as follows:
1 // Length of list 2 transient int size = 0; 3 4 // Chain header node 5 transient Node<E> first; 6 7 // List tail node 8 transient Node<E> last; 9 10 //Serialization tag 11 private static final long serialVersionUID = 876323262645176354L;
5, Constructor
LinkedList has two constructors, as follows:
(1) Nonparametric Construction:
1 public LinkedList() { 2 }
2) The construction method of creating a linked list from an existing set:
1 public LinkedList(Collection<? extends E> c) { 2 this(); 3 addAll(c); 4 }
Note: since the capacity of the linked list can be increased all the time, there is no constructor with specified capacity.
The first is a parameterless constructor.
The second is to construct a set using the specified set and call the addAll() method. Continue to follow up the method. The code is as follows:
1 public boolean addAll(Collection<? extends E> c) { 2 return addAll(size, c); 3 } 4 5 public boolean addAll(int index, Collection<? extends E> c) { 6 //1: Check whether the index range is within size 7 checkPositionIndex(index); 8 9 //2: The toArray () method stores the data of the collection into an object array 10 Object[] a = c.toArray(); 11 int numNew = a.length; 12 if (numNew == 0) 13 return false; 14 15 16 //3: Obtain the predecessor and successor nodes of the current linked list, and obtain the predecessor and successor nodes at the insertion position 17 Node<E> pred, succ; 18 //If the insertion position is the tail, the predecessor node is last and the successor node is null 19 if (index == size) { 20 succ = null; 21 pred = last; 22 } 23 //If it is not a tail node, get the node at the specified location, call the node() method to get the successor node, and then get the precursor node, 24 else { 25 succ = node(index); //Get current node 26 pred = succ.prev; //Get the precursor node of the current node 27 } 28 29 // 4: Loop inserts the elements in the array into the linked list 30 for (Object o : a) { 31 @SuppressWarnings("unchecked") E e = (E) o; 32 //Create a new node 33 Node<E> newNode = new Node<>(pred, e, null); 34 //If the insertion position is at the head of the linked list 35 if (pred == null) 36 first = newNode; 37 else 38 pred.next = newNode; 39 pred = newNode; 40 } 41 42 //If the insertion position is at the end, reset the last node 43 // If inserted to the end, the last element in the array is the tail node 44 if (succ == null) { 45 last = pred; 46 } 47 48 //Otherwise, connect the inserted linked list with the previous linked list 49 else { 50 // If inserted into the specified position, the last element in the array is associated with the next position 51 pred.next = succ; 52 succ.prev = pred; 53 } 54 size += numNew; 55 modCount++; 56 return true; 57 } 58 59 private void checkPositionIndex(int index) { 60 if (!isPositionIndex(index)) 61 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); 62 } 63 64 private boolean isPositionIndex(int index) { 65 return index >= 0 && index <= size; 66 }
As can be seen from the above, the addAll method usually includes the following four steps:
-
Check whether the index range is within size;
-
The toArray() method saves the data of the collection into the object array;
-
Obtain the precursor and successor nodes of the insertion position;
-
Traverse the data and insert the data into the specified location. If it is not at the end, link the original data;
The node(index) method is to obtain the node at the specified location. The code is as follows:
1 Node<E> node(int index) { 2 // assert isElementIndex(index); 3 // Judge where the subscript is. If the subscript is in the first half, traverse from front to back; Otherwise, traverse from back to front 4 if (index < (size >> 1)) { 5 Node<E> x = first; 6 for (int i = 0; i < index; i++) 7 x = x.next; 8 return x; 9 } else { 10 Node<E> x = last; 11 for (int i = size - 1; i > index; i--) 12 x = x.prev; 13 return x; 14 } 15 }
This method obtains the specified element by traversing the linked list.
It is worth noting that this method does not directly traverse the entire linked list from beginning to end, but first judge the position of the subscript. If it is in the first half, it will traverse from front to back; Otherwise, traverse from back to front. This can reduce the number of traversal nodes.
Because the memory space of the linked list is discontinuous, random access (subscript access) is not supported. Therefore, querying a node is realized by traversing the whole linked list.
6, Common methods
1. Add node method [tail interpolation]: add(), addLast(), offer(), offerLast()
Source code analysis:
1 public boolean add(E e) { 2 linkLast(e); 3 return true; 4 } 5 public void addLast(E e) { 6 linkLast(e); 7 } 8 public boolean offer(E e) { 9 return add(e); 10 } 11 public boolean offerLast(E e) { 12 addLast(e); 13 return true; 14 }
You can see that they are all implemented by calling the same method linkLast(e), as follows:
1 /** 2 * Links e as last element. 3 */ 4 void linkLast(E e) { 5 final Node<E> l = last; 6 // Create a node and point the prev pointer to the tail node of the linked list. 7 final Node<E> newNode = new Node<>(l, e, null); 8 9 // Point the last pointer to the newly created node. 10 last = newNode; 11 12 if (l == null) 13 // If the current linked list is empty, the header pointer will also point to this node. 14 first = newNode; 15 16 else 17 // If the linked list is not empty, insert the new node into the tail of the linked list 18 // Point the next pointer of the end node of the linked list to the new node, so as to fully realize the function of adding an element at the end of the linked list. 19 l.next = newNode; 20 size++; 21 modCount++; 22 }
This operation is to add the specified node to the end of the linked list.
2. New node [header insertion]: addFirst(), offerFirst()
Source code:
1 public void addFirst(E e) { 2 linkFirst(e); 3 } 4 public boolean offerFirst(E e) { 5 addFirst(e); 6 return true; 7 }
You can see that they are all implemented by calling the same method linkFirst(e), as follows:
1 /** 2 * Links e as first element. 3 */ 4 private void linkFirst(E e) { 5 final Node<E> f = first; 6 // Create a new element and point the next pointer of the element to the current header node 7 final Node<E> newNode = new Node<>(null, e, f); 8 // Point the header pointer to this node. 9 first = newNode; 10 if (f == null) 11 // If the current node is null, point the tail pointer to this node. 12 last = newNode; 13 else 14 // Point the prev pointer of the current header node to this node. 15 f.prev = newNode; 16 size++; 17 modCount++; 18 }
This code is the implementation of the linked list header to add elements.
3. New node [insert at specified location]: add(int index, E element)
Source code:
1 public void add(int index, E element) { 2 checkPositionIndex(index); 3 4 if (index == size) 5 linkLast(element); 6 else 7 linkBefore(element, node(index)); 8 }
There are two situations:
① If it just reaches the tail, insert it directly at the tail;
② If not at the tail, insert the element e before the non null node.
Source code:
1 void linkLast(E e) { 2 final Node<E> l = last; 3 final Node<E> newNode = new Node<>(l, e, null); 4 last = newNode; 5 if (l == null) 6 first = newNode; 7 else 8 l.next = newNode; 9 size++; 10 modCount++; 11 } 12 13 void linkBefore(E e, Node<E> succ) { 14 // assert succ != null; 15 final Node<E> pred = succ.prev; 16 final Node<E> newNode = new Node<>(pred, e, succ); 17 succ.prev = newNode; 18 if (pred == null) 19 first = newNode; 20 else 21 pred.next = newNode; 22 size++; 23 modCount++; 24 }
4. Set value: set(int index, E element)
Source code:
1 public E set(int index, E element) { 2 //Index check 3 checkElementIndex(index); 4 5 //Gets the element of the index 6 Node<E> x = node(index); 7 E oldVal = x.item; 8 x.item = element; 9 return oldVal; 10 }
5. Find value: get(int index)
Source code:
1 public E get(int index) { 2 checkElementIndex(index); 3 return node(index).item; 4 } 5 private void checkElementIndex(int index) { 6 if (!isElementIndex(index)) 7 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); 8 } 9 private boolean isElementIndex(int index) { 10 return index >= 0 && index < size; 11 }
You can see that the node() method above is called to find it.
6. Get header node:
Source code:
1 public E getFirst() { 2 final Node<E> f = first; 3 if (f == null) 4 throw new NoSuchElementException(); 5 return f.item; 6 } 7 public E element() { 8 return getFirst(); 9 } 10 public E peek() { 11 final Node<E> f = first; 12 return (f == null) ? null : f.item; 13 } 14 public E peekFirst() { 15 final Node<E> f = first; 16 return (f == null) ? null : f.item; 17 }
difference:
The difference between getFirst(),element(),peek(),peekFirst() and the four methods for obtaining header nodes lies in the handling of the empty linked list, whether to throw an exception or return null,
The getFirst() and element() methods will throw an exception when the linked list is empty; The interior of the element() method is implemented using getFirst(). They will throw NoSuchElementException when the linked list is empty;
The peek() and peekFirst() methods will return null when the linked list is empty;
7. Get tail node
1 public E getLast() { 2 final Node<E> l = last; 3 if (l == null) 4 throw new NoSuchElementException(); 5 return l.item; 6 } 7 public E peekLast() { 8 final Node<E> l = last; 9 return (l == null) ? null : l.item; 10 }
Difference between the two: when the linked list is empty, the getLast() method will throw NoSuchElementException, while peekLast() will not, but will return null.
8. Method of getting index based on object
int indexOf(Object o): traverse from scratch to find
1 public int indexOf(Object o) { 2 int index = 0; 3 if (o == null) { 4 //Ab initio traversal 5 for (Node<E> x = first; x != null; x = x.next) { 6 if (x.item == null) 7 return index; 8 index++; 9 } 10 } else { 11 //Ab initio traversal 12 for (Node<E> x = first; x != null; x = x.next) { 13 if (o.equals(x.item)) 14 return index; 15 index++; 16 } 17 } 18 return -1; 19 }
int lastIndexOf(Object o): traverse from the tail to find
1 public int lastIndexOf(Object o) { 2 int index = size; 3 if (o == null) { 4 //Traversal from tail 5 for (Node<E> x = last; x != null; x = x.prev) { 6 index--; 7 if (x.item == null) 8 return index; 9 } 10 } else { 11 //Traversal from tail 12 for (Node<E> x = last; x != null; x = x.prev) { 13 index--; 14 if (o.equals(x.item)) 15 return index; 16 } 17 } 18 return -1; 19 }
9. Method to check whether the linked list contains an object: contains()
Source code:
1 public boolean contains(Object o) { 2 return indexOf(o) != -1; 3 }
10. Method of deleting header node: remove(), removefirst(), pop(), poll(), pollFirst()
Source code:
1 public E remove() { 2 return removeFirst(); 3 } 4 5 public E pop() { 6 return removeFirst(); 7 } 8 9 public E removeFirst() { 10 final Node<E> f = first; 11 if (f == null) 12 throw new NoSuchElementException(); 13 return unlinkFirst(f); 14 } 15 16 public E poll() { 17 final Node<E> f = first; 18 return (f == null) ? null : unlinkFirst(f); 19 } 20 public E pollFirst() { 21 final Node<E> f = first; 22 return (f == null) ? null : unlinkFirst(f); 23 }
Essentially, the unlinkFirst() method is called
Source code:
1 private E unlinkFirst(Node<E> f) { 2 // assert f == first && f != null; 3 final E element = f.item; 4 final Node<E> next = f.next; 5 f.item = null; 6 f.next = null; // help GC 7 first = next; 8 if (next == null) 9 last = null; 10 else 11 next.prev = null; 12 size--; 13 modCount++; 14 return element; 15 }
11. Method of deleting tail node: removeLast(),pollLast()
Source code:
1 public E removeLast() { 2 final Node<E> l = last; 3 if (l == null) 4 throw new NoSuchElementException(); 5 return unlinkLast(l); 6 } 7 8 public E pollLast() { 9 final Node<E> l = last; 10 return (l == null) ? null : unlinkLast(l); 11 }
Difference: removeLast() will throw NoSuchElementException when the linked list is empty, while pollLast() method returns null.
Essentially, the unliklast () method is called.
Source code:
1 private E unlinkLast(Node<E> l) { 2 // assert l == last && l != null; 3 final E element = l.item; 4 final Node<E> prev = l.prev; 5 l.item = null; 6 l.prev = null; // help GC 7 last = prev; 8 if (prev == null) 9 first = null; 10 else 11 prev.next = null; 12 size--; 13 modCount++; 14 return element; 15 }
12. Delete specified element: remove (object o) & delete the element at the specified location: remove(int index)
1 public boolean remove(Object o) { 2 if (o == null) { 3 for (Node<E> x = first; x != null; x = x.next) { 4 if (x.item == null) { 5 unlink(x); 6 return true; 7 } 8 } 9 } else { 10 for (Node<E> x = first; x != null; x = x.next) { 11 if (o.equals(x.item)) { 12 unlink(x); 13 return true; 14 } 15 } 16 } 17 return false; 18 } 19 20 public E remove(int index) { 21 checkElementIndex(index); 22 return unlink(node(index)); 23 }
When deleting a specified object, you only need to call remove(Object o), but this method will delete only one matching object at a time. If the matching object is deleted, return true, otherwise false.
Essentially, the unlink(Node x) method is called:
1 E unlink(Node<E> x) { 2 // assert x != null; 3 final E element = x.item; 4 final Node<E> next = x.next;//Get successor nodes 5 final Node<E> prev = x.prev;//Get precursor node 6 7 //Delete precursor pointer 8 if (prev == null) { 9 first = next;//If the deleted node is a head node, make the head node point to the successor node of the node 10 } else { 11 prev.next = next;//Point the successor node of the predecessor node to the successor node 12 x.prev = null; 13 } 14 15 //Delete subsequent pointers 16 if (next == null) { 17 last = prev;//If the deleted node is a tail node, make the tail node point to the predecessor node of the node 18 } else { 19 next.prev = prev; 20 x.next = null; 21 } 22 23 x.item = null; 24 size--; 25 modCount++; 26 return element; 27 }
13. Serialization method: writeObject(java.io.ObjectOutputStream s)
Source code:
1 private void writeObject(java.io.ObjectOutputStream s) 2 throws java.io.IOException { 3 // Write out any hidden serialization magic 4 s.defaultWriteObject(); 5 6 // Write out size 7 s.writeInt(size); 8 9 // Write out all elements in the proper order. 10 for (Node<E> x = first; x != null; x = x.next) 11 s.writeObject(x.item); 12 }
14. Deserialization method: readObject(java.io.ObjectInputStream s)
Source code:
1 private void readObject(java.io.ObjectInputStream s) 2 throws java.io.IOException, ClassNotFoundException { 3 // Read in any hidden serialization magic 4 s.defaultReadObject(); 5 6 // Read in size 7 int size = s.readInt(); 8 9 // Read in all elements in the proper order. 10 for (int i = 0; i < size; i++) 11 linkLast((E)s.readObject()); 12 }
7, As other data structures
1. Implementation principle of FIFO (queue)
The principle of queue is to add elements from the end of the linked list and get elements from the head of the linked list every time. Just like queuing in life, there is always a first come first served.
Source code:
1 // Add an element at the end of the queue. It is recommended to use this as a convention. 2 publicboolean offer(E e){ 3 return add(e); 4 } 5 6 // Add an element at the end of the queue 7 publicboolean offerLast(E e){ 8 addLast(e); 9 return true; 10 } 11 12 // Both the lower layers of offer and offerLast call the linkLast method, which, as the name implies, adds elements to the tail of the linked list. 13 void linkLast(E e){ 14 finalNode<E> l =last; 15 16 // Create a node and point the prev pointer to the tail node of the linked list. 17 finalNode<E> newNode =newNode<>(l, e,null); 18 19 // Point the last pointer to the newly created node. 20 last= newNode; 21 22 if(l ==null) 23 // If the current linked list is empty, the header pointer will also point to this node. 24 first = newNode; 25 else 26 // Point the next pointer of the end node of the linked list to the new node, so as to fully realize the function of adding an element at the end of the linked list. 27 l.next= newNode; 28 29 size++; 30 modCount++; 31 } 32 33 // Delete an element at the head of the linked list. This is recommended 34 public E poll(){ 35 final Node<E> f = first; 36 return(f ==null)?null: unlinkFirst(f); 37 } 38 // Delete an element at the head of the linked list 39 public E pollFirst(){ 40 final Node<E> f = first; 41 return(f ==null)?null: unlinkFirst(f); 42 } 43 44 // The bottom layer of poll and pollFirst calls this method to delete the header element of the linked list. 45 private E unlinkFirst(Node<E> f){ 46 // assert f == first && f != null; 47 final E element = f.item; 48 final Node<E>next= f.next; 49 f.item =null; 50 f.next=null;// help GC 51 first =next; 52 if(next==null) 53 last=null; 54 else 55 next.prev =null; 56 size--; 57 modCount++; 58 return element; 59 } 60 61 // Gets the header element, but does not delete it. 62 public E peek(){ 63 final Node<E> f = first; 64 return(f ==null)?null: f.item; 65 }
More precisely, the linked list is a double ended linked list structure, which can operate nodes at both ends.
2. Implementation principle of LIFO (stack):
The principle of stack is that every time you add elements from the head, you also get elements from the head, and the later elements come out first. Just like we usually fold dishes, put them up one by one after washing, and then take them from top to bottom when we need them.
Source code:
1 // Add an element at the head of the linked list 2 publicvoid push(E e){ 3 addFirst(e); 4 } 5 6 // addFirst calls linkFirst. This code is the header of the linked list that implements the addition of elements. 7 private void linkFirst(E e){ 8 final Node<E> f = first; 9 // Create a new element and point the next pointer of the element to the current header node 10 final Node<E> newNode =newNode<>(null, e, f); 11 // Point the header pointer to this node. 12 first = newNode; 13 if(f ==null) 14 // If the current node is null, point the tail pointer to this node. 15 last= newNode; 16 else 17 // Point the prev pointer of the current header node to this node. 18 f.prev = newNode; 19 size++; 20 modCount++; 21 } 22 23 // Pop up the top node. 24 public E pop(){ 25 return removeFirst(); 26 } 27 28 // removeFirst calls unlinkFirst, which deletes the top element of the linked list 29 private E unlinkFirst(Node<E> f){ 30 // assert f == first && f != null; 31 final E element = f.item; 32 final Node<E>next= f.next; 33 f.item =null; 34 f.next=null;// help GC 35 first =next; 36 if(next==null) 37 last=null; 38 else 39 next.prev =null; 40 size--; 41 modCount++; 42 return element; 43 } 44 45 // Get the top node, but do not delete it 46 public E peek(){ 47 final Node<E> f = first; 48 return(f ==null)?null: f.item; 49 }
8, Iterator correlation
LinkedList has two Iterator implementations, one is the DescendingIterator that implements the Iterator interface, and the other is the ListItr that implements the ListIterator interface.
1,ListItr
Source code:
1 public ListIterator<E> listIterator(int index) { 2 checkPositionIndex(index); 3 return new ListItr(index); 4 } 5 6 private class ListItr implements ListIterator<E> { 7 private Node<E> lastReturned; 8 private Node<E> next; 9 private int nextIndex; 10 private int expectedModCount = modCount; 11 12 // When instantiating, point the next pointer to the element at the specified position 13 ListItr(int index) { 14 // assert isPositionIndex(index); 15 next = (index == size) ? null : node(index); 16 nextIndex = index; 17 } 18 19 public boolean hasNext() { 20 return nextIndex < size; 21 } 22 23 // Backward traversal 24 public E next() { 25 checkForComodification(); 26 if (!hasNext()) 27 throw new NoSuchElementException(); 28 29 lastReturned = next; 30 next = next.next; 31 nextIndex++; 32 return lastReturned.item; 33 } 34 35 public boolean hasPrevious() { 36 return nextIndex > 0; 37 } 38 39 // Forward traversal 40 public E previous() { 41 checkForComodification(); 42 if (!hasPrevious()) 43 throw new NoSuchElementException(); 44 45 lastReturned = next = (next == null) ? last : next.prev; 46 nextIndex--; 47 return lastReturned.item; 48 } 49 50 public int nextIndex() { 51 return nextIndex; 52 } 53 54 public int previousIndex() { 55 return nextIndex - 1; 56 } 57 58 public void remove() { 59 checkForComodification(); 60 if (lastReturned == null) 61 throw new IllegalStateException(); 62 63 Node<E> lastNext = lastReturned.next; 64 unlink(lastReturned); 65 if (next == lastReturned) 66 next = lastNext; 67 else 68 nextIndex--; 69 lastReturned = null; 70 expectedModCount++; 71 } 72 73 public void set(E e) { 74 if (lastReturned == null) 75 throw new IllegalStateException(); 76 checkForComodification(); 77 lastReturned.item = e; 78 } 79 80 public void add(E e) { 81 checkForComodification(); 82 lastReturned = null; 83 if (next == null) 84 linkLast(e); 85 else 86 linkBefore(e, next); 87 nextIndex++; 88 expectedModCount++; 89 } 90 91 public void forEachRemaining(Consumer<? super E> action) { 92 Objects.requireNonNull(action); 93 while (modCount == expectedModCount && nextIndex < size) { 94 action.accept(next.item); 95 lastReturned = next; 96 next = next.next; 97 nextIndex++; 98 } 99 checkForComodification(); 100 } 101 102 final void checkForComodification() { 103 if (modCount != expectedModCount) 104 throw new ConcurrentModificationException(); 105 } 106 }
2,DescendingIterator
The DescendingIterator iterator implements the function of traversing the linked list from the tail to the head. It reuses the previous method in ListItr, points the current position to the tail of the linked list, and then traverses forward one by one.
Source code:
1 private class DescendingIterator implements Iterator<E> { 2 private final ListItr itr = new ListItr(size()); 3 public boolean hasNext() { 4 return itr.hasPrevious(); 5 } 6 public E next() { 7 return itr.previous(); 8 } 9 public void remove() { 10 itr.remove(); 11 } 12 }
9, Different versions of LinkedList
Jdk1. In LinkedList Before 6, it was a two-way circular linked list, jdk1 7 cancels the cycle and adopts a two-way linked list.
1. Bidirectional linked list
Bidirectional linked list is a kind of linked list, also called double linked list bidirectional, that is, its link direction is bidirectional. It is composed of several nodes. Each node contains the pointer of the next node and the previous node. Therefore, starting from any node of the bidirectional linked list, you can easily access its predecessor node and successor node.
2. Bidirectional linked list features
-
- When creating a double linked list, you do not need to specify the length of the linked list.
- Compared with a single linked list, a double linked list needs an additional pointer to the precursor node, so it needs a little more storage space than a single linked list.
- To insert and delete a double linked list, you need to maintain the next and prev pointers at the same time.
- The elements in the double linked list need to be accessed sequentially, that is, to find the elements by traversal.
3. Bidirectional circular linked list
The head node and the tail node of the previous two-way linked list are not connected, so if you want to access the last node, you need to traverse from the beginning to the last node. Based on the two-way linked list, the prev pointer of the header node points to the last node, and the next pointer of the last node points to the header node, so a two-way circular linked list is formed.
More linked list operations: https://juejin.cn/post/6844903648154271757#heading-0
4,JDK6
Before JDK 1.7 (JDK1.6 is used here for example), LinkedList is a circular linked list implemented through headerEntry. First initialize an empty Entry as a header, and then connect end to end to form a circular linked list:
1 privatetransient Entry<E>header =new Entry<E>(null,null,null); 2 3 public LinkedList() {header.next =header.previous =header; }
Two basic attributes, size and header, are provided in the LinkedList.
1 private transient Entry<E> header = new Entry<E>(null, null, null); 2 private transient int size = 0;
- Where size represents the size of the LinkedList, header represents the header of the linked list, and Entry is the node object.
1 private static class Entry<E> { 2 E element; //Element node 3 Entry<E> next; //Next element 4 Entry<E> previous; //Previous element 5 6 Entry(E element, Entry<E> next, Entry<E> previous) { 7 this.element = element; 8 this.next = next; 9 this.previous = previous; 10 } 11 }
- The above is the source code of the entry object. Entry is the internal class of LinkedList, which defines the stored elements. The previous element and the next element of this element are typical two-way linked list definitions.
Every time an element is added / deleted, it is operated at the end of the chain by default. Here, the operation is in front of the header. Because the traversal is in the next direction, the operation in front of the header is equivalent to the operation at the end of the linked list.
As shown in the following insert operation addBefore and the figure, if obj is inserted_ 3. You only need to modify the header Previous and obj_2.next points to obj_3`
1 private Entry<E> addBefore(E e, Entry<E> entry) { 2 //Use the Entry constructor to build a new node newEntry, 3 Entry<E> newEntry = new Entry<E>(e, entry, entry.previous); 4 //Modify the references of the front and back nodes of the newEntry to ensure that the reference relationship of its linked list is correct 5 newEntry.previous.next = newEntry; 6 newEntry.next.previous = newEntry; 7 //Capacity + 1 8 size++; 9 //Modification times + 1 10 modCount++; 11 return newEntry; 12 }
- In the addBefore method, you just do this: build a new node newEntry, and then modify the references before and after it.
5,JDK7
In JDK 1.7 and 1.6, the headerEntry circular linked list is replaced with a non circular linked list composed of first and last.
1 transient int size = 0; 2 3 /** 4 * Pointer to first node. 5 * Invariant: (first == null && last == null) || 6 * (first.prev == null && first.item != null) 7 */ 8 transient Node<E> first; 9 10 /** 11 * Pointer to last node. 12 * Invariant: (first == null && last == null) || 13 * (last.next == null && last.item != null) 14 */ 15 transient Node<E> last;
[
- During initialization, there is no need to create a new Entry.
1 /** 2 * Constructs an empty list. 3 */ 4 public LinkedList() { 5 }
- When inserting / deleting, it is also the default operation at the end of the chain. Take the inserted obj as newLast and hang it behind oldLast. In addition, first judge whether first is empty. If it is empty, first = obj.
For example, the following insertion method, linkLast, operates at the tail. You only need to insert obj_3.next points to obj_4.
1 void linkLast(E e) { 2 final Node<E> l = last; 3 final Node<E> newNode = new Node<>(l, e, null); 4 last = newNode; 5 if (l == null) 6 first = newNode; 7 else 8 l.next = newNode; 9 size++; 10 modCount++; 11 }
among
1 private static class Node<E> { 2 E item; 3 Node<E> next; 4 Node<E> prev; 5 6 Node(Node<E> prev, E element, Node<E> next) { 7 this.item = element; 8 this.next = next; 9 this.prev = prev; 10 } 11 }
6. [1.6-header circular linked list] V.S [1.7-first/last acyclic linked list]
first/last in JDK 1.7 has the following advantages over previous header s:
-
(1) first / last has clearer concepts of chain head and chain tail, and the code looks easier to understand.
-
(2) The first / last mode can save new a headerEntry. (the instantiation of headerEntry is to make the following methods more uniform, otherwise there will be a lot of null verification of headers)
-
(3) Insert / delete at the head / tail of the chain. The first /last method is faster.
Insert / delete operations can be divided into two situations according to location: the middle and both ends.
Insert / delete in the middle. Both are the same. First traverse to find the index, and then modify the pointers at both ends of the linked list index.
At both ends, for a circular linked list, because it is connected end to end, you still need to deal with the pointers at both ends. Instead of a circular linked list, you only need to deal with one side, first previous/last. Next, so in theory, acyclic linked list is more efficient. Just at both ends (chain head / chain tail) operation is the most common
(for traversal, both are linked list pointer loops, so the traversal efficiency is the same.)
10, Thread safety
The concept of thread safety will not be repeated. Analyze the following scenarios:
If thread T1 traverses the LinkedList, thread T2 modifies it structurally.
The traversal of the LinkedList is realized through the listIterator(index) method, as follows:
1 public ListIterator<E> listIterator(int index) { 2 checkPositionIndex(index); 3 return new ListItr(index); 4 } 5 6 7 private class ListItr implements ListIterator<E> { 8 private Node<E> lastReturned; 9 private Node<E> next; 10 private int nextIndex; 11 // They are equal during initialization 12 private int expectedModCount = modCount; 13 14 15 ListItr(int index) { 16 // assert isPositionIndex(index); 17 next = (index == size) ? null : node(index); 18 nextIndex = index; 19 } 20 21 22 public E next() { 23 checkForComodification(); 24 if (!hasNext()) 25 throw new NoSuchElementException(); 26 27 28 lastReturned = next; 29 next = next.next; 30 nextIndex++; 31 return lastReturned.item; 32 } 33 34 35 public void remove() { 36 checkForComodification(); 37 if (lastReturned == null) 38 throw new IllegalStateException(); 39 40 41 Node<E> lastNext = lastReturned.next; 42 unlink(lastReturned); 43 if (next == lastReturned) 44 next = lastNext; 45 else 46 nextIndex--; 47 lastReturned = null; 48 expectedModCount++; 49 } 50 51 52 // ... 53 54 // Is there another thread to modify the structure of the current object 55 final void checkForComodification() { 56 if (modCount != expectedModCount) 57 throw new ConcurrentModificationException(); 58 } 59 }
Methods such as next(), add(e) of this class will check whether the modCount is consistent with the creation time (checkForComodification() method) during execution, so as to judge whether other threads have modified the structure of the object. If so, a ConcurrentModificationException will be thrown.
Therefore, LinkedList is thread unsafe.
11, Summary
1. LinkedList is a bi-directional linked List, which implements both the List interface and Deque interface. Therefore, it also has the properties of List, double ended queue and stack.
2. Thread unsafe.