Previously, we analyzed ArrayList. We know that ArrayList is implemented by arrays and traverses quickly, but when inserting and deleting, we need to move the elements behind, which is slightly less efficient. In this article, let's look at LinkedList, which is implemented by a two-way linked list. When inserting and deleting linkedList, we only need to change the pointers of the front and back nodes.
What is LinkedList
LinkedList is a two-way linked list inherited from AbstractSequential List. 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, which can use LinkedList as a double-ended queue.
LinkedList implements Cloneable interface, which covers the function clone(), and can clone.
LinkedList implements the java.io.Serializable interface, which means that LinkedList supports serialization and can be transmitted through serialization.
LinkedList is asynchronous.
Membership variables of LinkedList
//size of linked list
transient int size = 0;
//Point to the first node
transient Node<E> first;
//Point to the last node
transient Node<E> last;
Node
Node is the static inner class of LinkedList, and each node points to the former node and the latter node, which is the two-way linked list node.
private static class Node<E> {
// Values contained in the current node
E item;
//Next node
Node<E> next;
//Last node
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
Constructor of LinkedList
//Default constructor to create an empty list
public LinkedList() {
}
//Create a LinkedList with "c Collection"
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
LinkedList Method
1. Several key internal approaches
//Insert into the head
private void linkFirst(E e) {
//Head Node Assignment to f
final Node<E> f = first;
//Create a new node, and the header pointer points to the newly created node newNode
final Node<E> newNode = new Node<>(null, e, f);
//New Node assigns a value to the node first
first = newNode;
//If f is null, it means that there were no nodes in the previous list, so last points to newNode.
if (f == null)
last = newNode;
else
//The head of the first node (now the second) points to the new one.
f.prev = newNode;
size++;
modCount++;
}
//Add to the tail
void linkLast(E e) {
//Endpoint assignment to f
final Node<E> l = last;
//Create a new node, and the endpoint pointer points to the newly created node newNode
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
//If f is null, indicating that there were no nodes in the previous list, then the new node is the first node.
if (l == null)
first = newNode;
else
//The tail of the original tail node points to the new tail node
l.next = newNode;
size++;
modCount++;
}
//Insert an element before the specified node
void linkBefore(E e, Node<E> succ) {
// Gets a node preceding the specified node succ
final Node<E> pred = succ.prev;
//Create a new node with the head pointing to the node in front of succ and the tail pointing to the succ node with the data e
final Node<E> newNode = new Node<>(pred, e, succ);
//Let the node in front of succ point to the new node
succ.prev = newNode;
//If the node in front of succ.prev is empty, that means succ is the first node, then the new node will become the first node now.
if (pred == null)
first = newNode;
else
//If there are nodes ahead, point the new node to pred.next
pred.next = newNode;
size++;
modCount++;
}
//Delete the header node and return the value on that node
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
// Get the value of the node and assign it to the element
final E element = f.item;
//Get a node behind the header node
final Node<E> next = f.next;,
//Make the data on the head node empty and the tail point to null
f.item = null;
f.next = null; // help GC
//Set a node behind the header node to the first
first = next;
//If the node behind the head node is null, there is only one node in the list.
if (next == null)
last = null;
else
//If there is more than one node in the list, set next.prev to null
next.prev = null;
size--;
modCount++;
return element;
}
//Delete the tail node and return the value on that node
private E unlinkLast(Node<E> l) {
// assert l == last && l != null;
// Get the value of the node and assign it to the element
final E element = l.item;
// Get a node in front of the tail node
final Node<E> prev = l.prev;
//Make the data on the tail node empty and the head point to null
l.item = null;
l.prev = null; // help GC
//Set one node in front of the tail node to the last one
last = prev;
//If the node in front of the tail node is null, there is only one node in the list.
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
//Delete a specified node and return the value on that node
E unlink(Node<E> x) {
// assert x != null;
// Get the value of the node and assign it to the element
final E element = x.item;
//Gets a node behind the node
final Node<E> next = x.next;
//Get a node in front of that node
final Node<E> prev = x.prev;
//If there are no nodes in front, x is the first
if (prev == null) {
first = next;
} else {
//If there is a node in front, let the former node cross X and point directly to the node behind x
prev.next = next;
x.prev = null;
}
//If there are no nodes later, say x is the last node
if (next == null) {
last = prev;
} else {
//There are nodes at the back, so that the latter node points to the one in front of x.
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
//Gets the node at the specified location
Node<E> node(int index) {
// assert isElementIndex(index);
// Size >> 1. Move one representative to the right by dividing it by 2. Here we use a simple dichotomy to determine the distance between index and list.
if (index < (size >> 1)) {
Node<E> x = first;
// If index is less than half of size, traverse backwards from the head.
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
// If the index is greater than half of size, the previous traversal is backward from the tail.
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
2. Adding Elements
//Add elements to the head
public void addFirst(E e) {
linkFirst(e);
}
//Add elements to the tail
public void addLast(E e) {
linkLast(e);
}
//Add elements to the tail
public boolean add(E e) {
linkLast(e);
return true;
}
//Adding elements of a collection
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
//Add a collection at the specified location
public boolean addAll(int index, Collection<? extends E> c) {
//Check whether index is out of bounds
checkPositionIndex(index);
//Convert set c to an array
Object[] a = c.toArray();
int numNew = a.length;
if (numNew == 0)
return false;
//Create two nodes, pointing to the nodes before and after the insertion position, respectively
Node<E> pred, succ;
//Index = size indicates that it's the tail, and we need to add elements to the tail.
if (index == size) {
succ = null;
pred = last;
} else {
//Get the current node through node(index) and give succ, then get the previous node through succ.prev and assign it to pred
succ = node(index);
pred = succ.prev;
}
//Traversing through the array of contents to be added
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
//If pred is empty, the new one is the header node
if (pred == null)
first = newNode;
else
//Point newNode to pred.next
pred.next = newNode;
pred = newNode;
}
//If succ is null, indicating that the place to insert is the tail, pred is pointed to last
if (succ == null) {
last = pred;
} else {
//Otherwise pred points to the following element
pred.next = succ;
succ.prev = pred;
}
//Increased number of elements
size += numNew;
modCount++;
return true;
}
//Adding elements at specified locations
public void add(int index, E element) {
//Check whether index is out of bounds
checkPositionIndex(index);
//Index = size, indicating that it is already the tail, adding elements directly to the tail
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
public boolean offer(E e) {
return add(e);
}
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
public boolean offerLast(E e) {
addLast(e);
return true;
}
public void push(E e) {
addFirst(e);
}
3. Delete elements
//Delete the header node and return the value on that node
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
//Delete the tail node and return the value on that node
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
//Delete the node containing the specified element
public boolean remove(Object o) {
if (o == null) {
//Ergodic deletion
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;
}
//Clean up the bi-directional list
public void clear() {
// Clearing all of the links between nodes is "unnecessary", but:
// - helps a generational GC if the discarded nodes inhabit
// more than one generation
// - is sure to free memory even if there is a reachable Iterator
//Traversing backwards from the first node one by one
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
//Head and tail nodes are null
first = last = null;
size = 0;
modCount++;
}
//Delete the node at the specified location
public E remove(int index) {
//Check whether index is out of bounds
checkElementIndex(index);
return unlink(node(index));
}
//Delete the header node and return the deleted data
public E remove() {
return removeFirst();
}
//Delete the header node and return the deleted data
public E pop() {
return removeFirst();
}
//Delete the first o node in the list
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
//Reverse traversal to delete the node that first appears o
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;
}
3. Query, modify, whether to include elements
//Elements returning to the header node
public E getFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return f.item;
}
//Returns the element value of the tail node
public E getLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return l.item;
}
//Gets the element value of the node at the specified index location
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
//Returns the element value of the header node
public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
//Returns the element value of the header node
public E element() {
return getFirst();
}
//Retrieve the header node, return null if empty, return its element value if not empty and delete the header node
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
//Returns the element value of the header node
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
//Returns the element value of the tail node
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item;
}
//Retrieve the header node, return null if empty, return its element value if not empty and delete the header node
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
//Retrieve the tail node, return null if empty, return its element value if not empty, and delete the tail node
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l);
}
//Replace the element value of the node at the specified index location
public E set(int index, E element) {
checkElementIndex(index);
//Returns the node at the specified index location
Node<E> x = node(index);
E oldVal = x.item;
x.item = element;
return oldVal;
}
//Determine whether the list contains element o
public boolean contains(Object o) {
return indexOf(o) != -1;
}
//Returns the location of o for the first time in the list, or - 1 if it does not exist
public int indexOf(Object o) {
int index = 0;
if (o == null) {
//Traverse all nodes backwards from the beginning
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
//Reverse traversal, return to the first place where o appears, and return to -1 if none exists.
public int lastIndexOf(Object o) {
int index = size;
if (o == null) {
//Traverse all nodes forward from the tail node
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (x.item == null)
return index;
}
} else {
for (Node<E> x = last; x != null; x = x.prev) {
index--;
if (o.equals(x.item))
return index;
}
}
return -1;
}
4. Array Conversion
public Object[] toArray() {
//Create a new array of size
Object[] result = new Object[size];
int i = 0;
//Traverse all nodes
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
return result;
}
public <T> T[] toArray(T[] a) {
if (a.length < size)
//Create an array of strings a of size, of the same type as oldArray, which can also be used as an Object object in Java
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
int i = 0;
Object[] result = a;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
if (a.length > size)
a[size] = null;
return a;
}
5. iterator
//Inverse iterator calls descendingIterator() to get a backward-forward traversal of the LinkedList object.
public Iterator<E> descendingIterator() {
return new DescendingIterator();
}
/**
* Adapter to provide descending iterators via ListItr.previous
*/
private class DescendingIterator implements Iterator<E> {
private final ListItr itr = new ListItr(size());
public boolean hasNext() {
return itr.hasPrevious();
}
public E next() {
return itr.previous();
}
public void remove() {
itr.remove();
}
}
//A list traversal, which returns a list traversal that starts at a specified location by calling listIterator(int index).
public ListIterator<E> listIterator(int index) {
//Check whether index is out of bounds
checkPositionIndex(index);
//Returns a list traversal from the specified location
return new ListItr(index);
}
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned = null;
private Node<E> next;
private int nextIndex;
private int expectedModCount = modCount;
ListItr(int index) {
// assert isPositionIndex(index);
next = (index == size) ? null : node(index);
nextIndex = index;
}
//Determine if there is another node
public boolean hasNext() {
return nextIndex < size;
}
public E next() {
checkForComodification();
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next;
next = next.next;
nextIndex++;
return lastReturned.item;
}
//Judging whether there is a previous node
public boolean hasPrevious() {
return nextIndex > 0;
}
public E previous() {
checkForComodification();
if (!hasPrevious())
throw new NoSuchElementException();
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--;
return lastReturned.item;
}
public int nextIndex() {
return nextIndex;
}
public int previousIndex() {
return nextIndex - 1;
}
public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
Node<E> lastNext = lastReturned.next;
unlink(lastReturned);
if (next == lastReturned)
next = lastNext;
else
nextIndex--;
lastReturned = null;
expectedModCount++;
}
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e;
}
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null)
linkLast(e);
else
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
@Override
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (modCount == expectedModCount && nextIndex < size) {
action.accept(next.item);
lastReturned = next;
next = next.next;
nextIndex++;
}
checkForComodification();
}
//Similar to ArrayList, it uses an attribute modCount to determine whether the list is structurally changed after the traversal is obtained, and if so, throws a Concurrent ModificationException exception.
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
6.LinkedList traversal
public class Test {
/**
* @param args
*/
public static void main(String[] args) {
// Traversing LinkedList through Iterator
iterator(getLinkedListData());
// Traversing LinkedList through Quick Random Access
for1(getLinkedListData());
// Traversing LinkedList through a forEast loop
for2(getLinkedListData());
}
private static LinkedList getLinkedListData() {
LinkedList llist = new LinkedList();
for (int i = 0; i < 100000; i++)
llist.addLast(i);
return llist;
}
/**
* Traversing LinkedList through an iterator
*/
private static void iterator(LinkedList<Integer> list) {
// Record start time
long start = System.currentTimeMillis();
Iterator iter = list.iterator();
while (iter.hasNext()) {
iter.next();
}
// End time of recording
long end = System.currentTimeMillis();
long interval = end - start;
System.out.println("Iterator traversal:" + interval + " ms");
}
/**
* Traversing LinkedList through Quick Random Access
*/
private static void for1(LinkedList<Integer> list) {
// Record start time
long start = System.currentTimeMillis();
int size = list.size();
for (int i = 0; i < size; i++) {
list.get(i);
}
// End time of recording
long end = System.currentTimeMillis();
long interval = end - start;
System.out.println("Quick random access:" + interval + " ms");
}
/**
* Traversing LinkedList through the forEast loop
*/
private static void for2(LinkedList<Integer> list) {
// Record start time
long start = System.currentTimeMillis();
for (Integer integ : list){
}
// End time of recording
long end = System.currentTimeMillis();
long interval = end - start;
System.out.println("forEach Circulation:" + interval + " ms");
}
}
Operation results:
As can be seen from the above results, iterators or forEach are most efficient when traversing LinkedList to get element values. Quick random access is inefficient, so it's better not to traverse LinkedList through random access.
The difference between ArrayList and LinkedList is:
ArrayList
The bottom layer is based on arrays, which supports random access, high efficiency of query and data acquisition.
After inserting or deleting an element index, elements are moved (backward or forward), so adding/deleting data is inefficient.
With capacity, each time the threshold is reached, it needs to be expanded, and at the end of the list a certain capacity space will be reserved.
LinkedList
The bottom layer is based on a double-ended circular list, which does not support random access. Adding or deleting elements will only affect the two nodes around it. The cost is very low, so it is efficient to add or delete elements.
It can only traverse sequentially and can not get elements according to the index, so the query efficiency is not high.
No fixed capacity, no expansion; but it needs more memory, because each node needs to store the information of the front and back nodes, so every element of it needs to consume considerable space.
Conclusion:
ArrayList and LinkedList have their own advantages and disadvantages, so when we need to find elements frequently, we can choose ArrayList, which is more efficient. If we need to add or delete lists frequently, it is better to choose LinkedList.