Statement: respect other people’s labor achievements, reproduced please attach original link!

1. Linkedin List inheritance



LinkedListLinkedList is a List implemented as a two-way LinkedList. It can be used as a queue or stack in addition to being a List.

LinkedList implements both Cloneable and Serializable interfaces, indicating that it can be cloned and serialized. Similarly, when LinkedList is cloned, like ArrayList both are shallow copies. For how to implement shallow copy and serialization of collections, I in the last article of the JDK source ArrayList parsing has been introduced to you, you can refer to.

Let’s get down to business — analyzing the source code:

2. Basic attributes of LinkedList

// The number of elements in the list
transient int size = 0;
// The first node of the list
transient Node<E> first;
// The last node of the list
transient Node<E> last;
Copy the code

The three basic attributes are transiently decorated with the keyword so that they are not serialized.

3. The Node inner classes

private static class Node<E> {
    E item;// The element stored in node
    Node<E> next;/ / precursor
    Node<E> prev;/ / after flooding
    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev; }}Copy the code

As you can see from the code, this is a bidirectional list structure.

4. Construction method

Empty and structure

public LinkedList(a) {}Copy the code

Parameter construction (parameter is a Collection)

public LinkedList(Collection<? extends E> c) {
    this(a); addAll(c); }// Appends all elements in the specified collection to the end of this list
public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}
// Inserts all elements from the specified collection into the collection starting at the specified location
public boolean addAll(int index, Collection<? extends E> c) {// Inserts all elements from the specified collection into the collection starting at the specified location
    checkPositionIndex(index);// check if index >= 0 && index <= size
    Object[] a = c.toArray();// Convert the collection to an array of type Object
    int numNew = a.length;// Get the length of the array
    if (numNew == 0)
        return false;
    Node<E> pred, succ;
    if (index == size) {// When the index to be inserted is at the last element of the list
        succ = null;// The current node is empty
        pred = last;// The first node of the collection to be joined is the last node of the original list
    } else {
        succ = node(index);// Get the index position of the node
        pred = succ.prev;// The precursor points to the previous node of the index node
    }
    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o;
        Node<E> newNode = new Node<>(pred, e, null);
        if (pred == null)// If the precursor is NULL, the current node is at the head of the list
            first = newNode;// Make the head node equal to the newly created node
        else
            pred.next = newNode;// The tail pointer of the precursor node points to the head node
        pred = newNode;// Pred moves forward and backward
    }
    if (succ == null) {// The index is at the end of the original list
        last = pred;// The tail node points directly to the end of the spliced completion list
    } else {
        pred.next = succ;// The back-end node of pred is succ
        succ.prev = pred;// The precursor of suCC is pred
    }
    size += numNew;// Number of elements in the set +numNew
    modCount++;// Set changes +1
    return true;
}
// Returns the (non-empty) node at the specified element index.
Node<E> node(int index) {
    if (index < (size >> 1)) {// If the index is in the first half of the list element
        Node<E> x = first;// Locate index from the beginning node
        for (int i = 0; i < index; i++)
            x = x.next;// Move the pointer to index
        return x;// Return the contents of this object
    } else {// If the index is in the last half of the list element
        Node<E> x = last;// Locate the index position from the tail node
        for (int i = size - 1; i > index; i--)
            x = x.prev;// Move the pointer to index
        return x;// Return the contents of this object}}Copy the code

Given by two no-parameter constructors, this is an unbounded two-ended queue.

5. Add elements

As for the nature of the two-ended queue, one way to add elements is to add elements at the end of the queue, and the other way is to add elements at the head of the queue. These two forms are mainly implemented in the LinkedList through the following two methods:

public void addFirst(E e) {
    linkFirst(e);// Add header
}
public void addLast(E e) {
      linkLast(e);// add a tail
}
// As an unbounded queue, adding elements always succeeds
public boolean offerFirst(E e) {
    addFirst(e);
    return true;
}
public boolean offerLast(E e) {
    addLast(e);
    return true;
}
Copy the code

Add the head

/** * adds the element */ to the header
private void linkFirst(E e) {
    final Node<E> f = first;/ / the first node
    final Node<E> newNode = new Node<>(null, e, f);// Create a new node, next of which is the first node
    first = newNode;// make the new node the new first node
    if (f == null)// Determine if the element is the first to be added
        last = newNode;// If so, set last to the new node
    else// If not, set the prev pointer of the original first node to the new node
        f.prev = newNode;// The first node is preceded by the new node
    size++;// The number of elements increases by 1
    modCount++;// The number of changes added by 1 indicates that this is a fail-fast set
}
Copy the code

Add a tail

/** ** add */ to the end
void linkLast(E e) {
    final Node<E> l = last;// End of queue
    final Node<E> newNode = new Node<>(l, e, null);// Create a new node whose prev is the tail node
    last = newNode;// Make the new node the new tail node
    if (l == null)// Determine if the element is the first to be added
        first = newNode;// If yes, set first as the new node
    else// Otherwise, set the next pointer to the last node to the new node
        l.next = newNode;
    size++;// The number of elements increases by 1
    modCount++;// The number of changes added by 1 indicates that this is a fail-fast set
Copy the code

Add at the middle specified position

LinedList, as a collection, needs to add elements elsewhere in the middle. This is done using the following method:

// Add the element at the specified index position
public void add(int index, E element) {
    checkPositionIndex(index);// Determine if the boundary is crossed,
    if (index == size)// If index is a position after the last node of the queue
        linkLast(element);// Add the new node directly after the last node
    else// Otherwise call linkBefore() to add nodes in the middle
        linkBefore(element, node(index));
}
Returns the (non-empty) node at the specified element index.
Node<E> node(int index) {
    // Because it is doubly linked
    if (index < (size >> 1)) {// If the index is in the first half of the list element
        Node<E> x = first;// Locate index from the beginning node
        for (int i = 0; i < index; i++)
            x = x.next;// Move the pointer to index
        return x;// Return the contents of this object
    } else {// If the index is in the last half of the list element
        Node<E> x = last;// Locate the index position from the tail node
        for (int i = size - 1; i > index; i--)
            x = x.prev;// Move the pointer to index
        return x;// Return the contents of this object}}void linkBefore(E e, Node<E> succ) {// Insert element e before the non-empty node succ
    // succ is the successor of the node to be added
    final Node<E> pred = succ.prev;// Find the front node of the node to be added
    final Node<E> newNode = new Node<>(pred, e, succ);// Create a new node between its front node and its successor
    succ.prev = newNode;// Modify the leading pointer to the new node
    if (pred == null)// Check whether the front node is empty
        first = newNode;// If it is null, it is the first element to be added
    else// Otherwise, change next of the front node to the new node
        pred.next = newNode;
    size++;// Change the number of elements in the set
    modCount++;// The number of set changes is increased by 1
}
Copy the code

LinkedList to add elements in the middle of the implementation principle is a typical double LinkedList to add elements in the middle of the process!

As shown in figure:

  • Adding elements at the beginning and end of the queue is efficient, O(1) time
  • It is inefficient to add elements in the middle. First, we need to find the nodes at the insertion position, and then modify the Pointers of the nodes before and after. The time complexity is O(n).

6. Delete elements

As a double-ended queue, there are two ways to delete elements, one is to delete elements at the beginning of the queue, the other is to delete elements at the end of the queue. As a List, you need to support intermediate deletion of elements, so there are three methods for deleting elements, respectively as follows. Remove the header element:

Remove the head

public E removeFirst(a) {// remove throws an exception if no element is present
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

private E unlinkFirst(Node<E> f) {// Delete the first node
    // assert f == first && f ! = null;
    final E element = f.item;// The element value of the first node
    final Node<E> next = f.next;// Next pointer to the first node
    f.item = null;
    f.next = null; // Assist GC collection
    first = next;// make next of the first node as the new first node
    if (next == null)// If there is only one element, delete it and set last to null
        last = null;
    else// Otherwise, set next's leading pointer to null
        next.prev = null;
    size--;// The number of elements is reduced by 1
    modCount++;// The number of changes is increased by 1
    return element;// Returns the deleted element
}
Copy the code

The tail to delete

public E removeLast(a) {// remove throws an exception if no element is present
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}

private E unlinkLast(Node<E> l) {// Delete the tail node
    // assert l == last && l ! = null;
    final E element = l.item;// The element value of the last node
    final Node<E> prev = l.prev;// The leading pointer to the tail node
    l.item = null;
    l.prev = null; // Clear the contents of the tail node to assist GC
    last = prev;// Make the front node the new tail node
    if (prev == null)// If there is only one element, delete and set first to null
        first = null;
    else// Otherwise empty the front node next
        prev.next = null;
    size--;// The number of elements is reduced by 1
    modCount++;// The number of changes is increased by 1
    return element;// Returns the deleted element
}
Copy the code

Delete the middle specified position

public E remove(int index) {// Delete the intermediate node at index
    checkElementIndex(index);// Check for overbounds
    return unlink(node(index));// Delete the index node
}

/** * Delete node x. */
E unlink(Node<E> x) {
    // assert x ! = null;
    final E element = x.item;// The element value of x
    final Node<E> next = x.next;// the post-node of x
    final Node<E> prev = x.prev;// The front node of x
    if (prev == null) {// If the front node is empty
        first = next;// specify the first node, so that first points to the node after x
    } else {// Otherwise change next of the front node to the back node of x
        prev.next = next;
        x.prev = null;// the prefix of x is empty
    }
    if (next == null) {// If the back node is empty
        last = prev;// Last points to the front node of x
    } else {// Otherwise, change the prev of the rear node to the front node of X
        next.prev = prev;
        x.next = null;// x is null after x
    }
    x.item = null;// Empty the element value of x to assist GC
    size--;// The number of elements is reduced by 1
    modCount++;// The number of changes is increased by 1
    return element;// Returns the deleted element
}
Copy the code

7. Modify elements

Modifying elements by index is simple:

public E set(int index, E element) {
    checkElementIndex(index);
    Node<E> x = node(index);
    E oldVal = x.item;
    x.item = element;
    return oldVal;
}
Copy the code

8. Can be used as a queue

PollFirst (), pollLast(), peekLast(), peekFirst(), pollFirst(), pollLast(), peekFirst(), and so on. The implementation principle is the same. Here are two simple examples:

// poll returns null if there are no elements
public E pollFirst(a) {
    final Node<E> f = first;
    return (f == null)?null : unlinkFirst(f);
}
// poll returns null if there are no elements
public E pollLast(a) {
    final Node<E> l = last;
    return (l == null)?null : unlinkLast(l);
}
Copy the code

The three methods for deleting elements are typical double-linked list deletion methods, and the general process is shown in the figure below:

  • Removing elements at the beginning and end of the queue is efficient and takes O(1) time.
  • It is inefficient to delete elements in the middle. First, it is necessary to find the node at the deleted position, and then modify the pointer before and after. The time complexity is O(n).

9. Can be used as a stack

LinkedList is a two-ended queue, which can be used as a stack. It also has the following methods:

// Pop up the trailing element
public E pop(a) {
	// The first element of the queue is the last element of the stack
    return removeFirst();
}
/ / into the stack
public void push(E e) {
	// The first element of the queue is the last element of the stack
    addFirst(e);
}
Copy the code

10. Summary

1) LinkedList is a List implemented as a double-linked List; 2) LinkedList is also a double-ended queue, with queue, double-ended queue, stack characteristics; 3) The 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) Adding and deleting elements in the middle of LinkedList is inefficient, and the time complexity is O(n); 5) LinkedList does not support random access, so accessing non-queue elements is inefficient; 6) LinkedList equals ArrayList + ArrayDeque in function;

ArrayList represents a typical implementation of List, LInkedList represents a typical implementation of Deque, and LInkedList implements List.

11. Extension

Abnormal concurrent modification of LinkedList

For example:

@Test
public void test02(a) {
    LinkedList linkedList = new LinkedList();
    linkedList.add("aaa");
    linkedList.add("bbb");
    linkedList.add("ccc");
    Iterator iterator = linkedList.iterator();
    while (iterator.hasNext()){// Add or remove the list while iterating through it
        linkedList.add("eee");// A concurrent modification exception occursSystem.out.println(iterator.next()); }}Copy the code

If the list structure is changed while iterating through the iterator, a concurrent modification exception occurs:

java.util.ConcurrentModificationException at java.util.LinkedList$ListItr.checkForComodification(LinkedList.java:966) at  java.util.LinkedList$ListItr.next(LinkedList.java:888) at com.haust.list.LinkedListTest.test02(LinkedListTest.java:35)Copy the code

Linkedlist.iterator (); Click on the source code layer by layer to enter the AbstractList class and see the following code:

public ListIterator<E> listIterator(final int index) {
    rangeCheckForAdd(index);// Check if index is out of bounds
    return new ListItr(index);
}
Copy the code

ListItr(index) = ListItr(index)

// modCount=3 here because we added three elements to the collection when initializing linkedList
// They are aaa, BBB, CCC
// Make the expected number of changes equal to the number of linked list changes
private int expectedModCount = modCount;
// So now expectedModCount is executing after linkedList.iterator(); The expected number of changes after that is 3
Copy the code

When linkedList.iterator() is finished; Iterator.hasnext (), then go inside the loop and execute add, calling:

public boolean add(E e) {
        linkLast(e);
        return true;
}
void linkLast(E e) {
    final Node<E> l = last;// End of queue
    final Node<E> newNode = new Node<>(l, e, null);/ / create a new
    last = newNode;// Make the new node the new tail node
    if (l == null)// Determine if the element is the first to be added
        first = newNode;// If yes, set first as the new node
    else// Otherwise, set the next pointer to the last node to the new node
        l.next = newNode;
    size++;// The number of elements increases by 1
	/** note: the number of changes to the list is increased by 1, which is an important condition for concurrent modification exceptions **/
    modCount++;// Add 1 here, the actual list changes from 3- > 4
}
Copy the code

Next () : iterator.next() : iterator.next() : iterator.next()

public E next(a) {
	// Check whether the expected and actual list changes match
    checkForComodification();
    if(! hasNext())throw new NoSuchElementException();
    lastReturned = next;
    next = next.next;
    nextIndex++;
    return lastReturned.item;
}
final void checkForComodification(a) {
    // The actual number of changes 4 is not equal to the expected number of changes
    if(modCount ! = expectedModCount)// Throw a concurrent modification exception
        throw new ConcurrentModificationException();
}
Copy the code

Solution:

// Use ListIterator instead of Iterator
ListIterator listIterator = linkedList.listIterator()
while (listIterator.hasNext()){
    listIterator.add("eee");// Add elements to the collection using listIterator
    System.out.println(iterator.next());
}
Copy the code

Output result (normal) :

aaa
bbb
ccc
Copy the code

ListIterator ListIterator () is essentially the same as iterator(). Listiterator.add (“eee”); ListIterator () is the same as iterator(). The code snippet adds elements as follows:

public void add(E e) {
	// Check whether there are concurrent modification exceptions before adding. In this case, the expected and actual modification times are the same, which are both 3
    checkForComodification();
    lastReturned = null;
    if (next == null)
        linkLast(e);
    else
        linkBefore(e, next);// modCount++ changes to 4
    nextIndex++;
    // That's the point: when the add is done, the expected number of changes is also +1, which ensures that it is synchronized with the actual number of changes +1, both of which are currently 4
    expectedModCount++;
}
Copy the code

If the article is helpful to you, please also connect three keys!