Java collection dry goods - LinkedList source code analysis

foreword

In the last article, we have a detailed analysis of ArrayList, and today we will talk about LinkedList. What is the difference between them? The biggest difference is that the implementation of the underlying data structure is different. ArrayList is implemented by an array (see the previous article for details), and LinedList is implemented by a linked list. As for some other differences, it can be said that most of them are different applications derived from different essences.

LinkedList

linked list

Before analyzing LinedList, make a brief introduction to the linked list. After all, the linked list is not used as much as the array, so it is inevitable that many people are not familiar with it.

A linked list is a basic linear data structure, which is linear as an array, but an array is linear in physical storage in memory and is also linear in logic; while a linked list is only linear in logic. In each storage unit of the linked list, not only the current element is stored, but also the address of the next storage unit, so that all storage units can be connected together by address.

Every time you search, you can find the required element through the first storage unit. Performing a delete operation simply disconnects the reference to the relevant element. The schematic diagram is as follows:

2018-01-10_114030

2018-01-10_114053

2018-01-10_114109

Of course? What is used in LinkedList is not the most basic singly linked list, but a doubly linked list.

There is a basic storage unit in LinedList, which is an inner class of LinkedList. There are two attributes in the node element, which respectively save the reference of the previous node and the next node.

~java
//Static inner class
private static class Node {
//Storage element's attribute
E item;
//Front and back nodes refer to
Node next;
Node prev;
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
~

definition

~java
public class LinkedList
extends AbstractSequentialList
implements List, Deque, Cloneable, java.io.Serializable
~

The definition is similar to ArrayList, but it should be noted that LinkedList implements Deque (indirectly implements the Qeque interface). Deque is a bidirectional pair of columns, which provides a method for LinedList to access elements from both ends of the pair of columns.

initialization

When analyzing ArrayList, we know that the initialization length of ArrayList when using the parameterless construction method is 10, and all collections constructed without parameters will point to the same object array (static constant, located in the method area), so what is the initialization of LinkedList? Woolen cloth?

Open no-argument constructor

~java
public LinkedList() {
}
~

There is nothing, so we can only look at the properties.

~java
//Initialization length is 0
transient int size = 0;
//There are front and rear nodes
transient Node first;
transient Node last;
~

Icon initialization

~java
LinkedList list = new LinkedList();
String s = "sss";
list.add(s);
~

2018-01-11_110237

method

add (E and)

~java
public boolean add(E e) {
linkLast(e);
return true;
}
~

From the method, we know that after calling the add method, it is not added immediately, but the linkLast method is called. As the name implies, the new element is added at the end of the collection.

~java
void linkLast(E e) {
// Assign (pass by reference) the last element to the node l final modifier and cannot be changed after the attribute is assigned
final Node l = last;
// Call the node's parameterized constructor to create The new node saves the added element
final Node newNode = new Node<>(l, e, null);
//At this time, the new node is the last element. Assign the new node to last
last = newNode;
//If l is null, it means This is the first time to add an element, then assign first as a new node. This list has only one element to store the element. The start element and the last element are the same element
if (l == null)
first = newNode;
else
//If it is not the first Add, assign the new node to the next of l (the last element before adding)
l.next = newNode;
//Length+1
size++;
//Modification times+1
modCount++;
}
~

From the above code we can see that it does not rely on subscripts when adding elements.

The processing is to save the information of the last node (actually the last node) through a last (Node object), and each time the last element is continuously changed to realize the addition of elements. (To fully understand this, you need to understand the difference and essence of java value passing and reference passing).

add(int index, E element)

add to the specified location

~java
public void add(int index, E element) {
//Subscript out-of-bounds check
checkPositionIndex(index);
//If it is added to the end, call linkLast directly
if (index == size)
linkLast(element);
//Call otherwise linkBefore
else
linkBefore(element, node(index));
}
//Insert an element before the specified element
void linkBefore(E e, Node succ) {
// assert succ != null; Assume assert succ is not null
//define a node The element saves the prev reference of succ, that is, its previous node information
final Node pred = succ.prev;
//Create a new node node element as the element to be inserted e prev reference is pred, that is, the previous element of succ before inserting next is succ
final Node newNode = new Node<>(pred, e, succ);
//At this time, the previous node of succ is the new node inserted, so modify the node to point to
succ.prev = newNode;
//If pred is null, it means this is first element
if (pred == null)
//member property first points to the new node
first = newNode;
//otherwise
else
//the next property of the element before the node points to the new node
pred.next = newNode;
//length+1
size++;
modCount++;
}
~

Node element insertion icon

LinkedList1

LinkedList2

In the above code, we should have noticed that LinkedList also needs to perform certain verification when inserting elements, that is, subscript out-of-bounds verification. Let's take a look at the specific implementation.

~java
private void checkPositionIndex(int ​​index) {
if (!isPositionIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//If the input index is within the range, return true
private boolean isPositionIndex(int ​​index) {
return index >= 0 && index <= size;
}
~

Through the analysis of the two adding methods, we can clearly feel the efficiency of adding elements to LinkedList, no need to expand, no need to copy the array.

get

~~~java
public E get(int index) {
//Checking whether the subscript element exists is actually checking whether the subscript is out of bounds
checkElementIndex(index);
//If there is no out of bounds, return the item corresponding to the subscript node, which is the corresponding element
return node(index).item;
}

//If the subscript out of bounds check, throw an exception
private void checkElementIndex(int ​​index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isElementIndex(int ​​index) {
return index >= 0 && index < size;
}
~~~

~~~java
//This method is used to return the non-empty node of the specified subscript
Node node(int index) {
//Assuming that the subscript is not out of bounds, it is not actually out of bounds. After all, the subscript out of bounds check is performed before this
// assert isElementIndex(index);

//If the index is less than one-half of the size, the search starts from the front (backward search), otherwise it is searched forward
if (index < (size >> 1)) {//The left shift is efficient and worth learning
Node x = first;
// Traversal
for (int i = 0; i < index; i++)
//The next of each node is his next node reference while traversing, and x will continue to be assigned as the next element of the node. Traversing to the index is to get is the element of the node corresponding to index
x = x.next;
return x;
} else {
Node x = last;
for (int i = size - 1; i > index; i–)
x = x.prev;
return x;
}
}
~~~

This code fully reflects the superiority of the doubly linked list, which can be traversed from the front or the back, and the efficiency can be significantly improved by judging the index range. However, when traversing, you can clearly see the inefficiency of the LinkedList get method to obtain elements, and the time complexity is O(n).

remove(int index)

The so-called deletion of a node is to set the front and back references of the node to null, and ensure that no other nodes point to the deleted node.

~java
public E remove(int index) {
//Subscript out-of-bounds check
checkElementIndex(index);
//The return value here is actually two methods executed
//node gets the specified subscript non-empty node
//unlink break Open the connection of the specified node
return unlink(node(index));
}
~

~java
E unlink(Node x) {
//Assume x is not null
// assert x != null;
//Define a variable element to accept the elements in the x node and finally return the final value return
final E element = x.item;
/ /Define two nodes to obtain the reference of the nodes before and after the x node respectively
final Node next = x.next;
final Node prev = x.prev;
//If the reference before the node is null, it means this is the first node
if (prev == null ) {
//x is the first node to be deleted, then first needs to be reassigned
first = next;
} else {
//If x is not the first node, point the next of prev (the previous node of x) to x's The next node (bypassing x)
prev.next = next;
//The front reference of x is assigned null
x.prev = null;
}
//If the back reference of the node is null, it means that this is the last node in a series of processing similar to the previous reference The method will not repeat
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
//Assign the element in the x node to null
x.item = null;
size--;
modCount++;
return element;
}
~

illustrate

  1. prev, item, next are all set to null to allow the virtual machine to recycle
  2. We can see that the efficiency of LinkedList deleting elements is not bad
LinkedListSummary
  1. The query speed is not good, and each search needs to be traversed. This is the sequential subscript traversal mentioned in the ArrayList analysis.
  2. Adding elements and deleting have speed advantages
  3. Implement the column interface

Difference between ArrayList and LinkedList

  1. Sequential insertion, both are fast, but ArrayList is slightly faster than LinkedList, array implementation, array is created in advance; LinkedList needs to renew new nodes every time
  2. LinedList needs to maintain front and rear nodes, which will consume more memory
  3. Traversal, LinedList is suitable for iterative traversal; ArrayList is suitable for loop traversal
    1. Don't use a normal for loop to iterate through the LinedList
    2. Don't use iterative traversal to traverse the ArrayList (see the previous article "ArrayList Analysis" for details)
  4. Not to mention deletion and insertion, after all, ArrayList needs to copy the array and expand.

I can't guarantee that every place is right, but I can guarantee that every sentence, every line of code has been scrutinized and considered. I hope that behind every article is my attitude of pursuing a purely technical life.

Always believe that good things are about to happen.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325480371&siteId=291194637