Source code detailed data structure Linked List

Abstract: java.util.LinkedList is one of the members of the Java collection framework. The bottom layer is implemented based on a doubly linked list, and the collection capacity can be dynamically changed.

This article is shared from HUAWEI CLOUD Community " LinkedList Source Code Analysis ", author: Chen Pi's JavaLib.

Introduction to LinkedList

java.util.Linked List is one of the members of the Java collection framework. The bottom layer is implemented based on a doubly linked list, and the collection capacity can be dynamically changed. It inherits from the Abstract Sequential List abstract class and implements the List interface. It also implements three marker interfaces, Cloneable and Serializable, indicating that Array List is cloneable and serializable.

Array List The bottom layer of the array list is implemented based on dynamic arrays, so the advantage is that it can support fast random access, but the addition and deletion operations may be slower (because array expansion and data copying may be required). Moreover, the array needs to apply for a certain memory space first, which may cause waste. The advantage of LinkedList is that addition and deletion operations are faster, and as many elements are stored in the list, as many nodes are dynamically applied for storage, which saves memory space.

Why use a doubly linked list? The main reason is that the traversal efficiency is higher than that of a singly linked list. For example, when we need to find the node with the specified subscript, when adding, deleting, or modifying the specified subscript, we first determine whether the position is close to the head or the tail, and then decide to start the search from the head or the tail to improve efficiency.

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable {

}

2 LinkedList source code analysis

2.1 Internal variables

The elements of LinkedList are stored in the node object. The node class is an internal private static class of the LinkedList class. The source code is as follows:

private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

Three variables are defined in LinkedList, one represents the number of elements in the current list, and the other two variables point to the head and tail of the linked list. As well as the modCount variable in its parent class AbstractList, each addition, deletion or modification to the linked list will increase it by 1.

transient int size = 0;

transient Node<E> first;

transient Node<E> last;

protected transient int modCount = 0;

2.2 Constructor

ArrayList has 2 constructors, a no-argument constructor and a constructor that constructs a collection using the specified Collection collection.

No-argument constructor, which does nothing.

public LinkedList() {}

Use the specified Collection to construct a linked list. If the Collection cannot be null, it will throw npe.

public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}

public boolean addAll(int index, Collection<? extends E> c) {
    checkPositionIndex(index);

    Object[] a = c.toArray();
    int numNew = a.length;
    if (numNew == 0)
        return false;

    Node<E> pred, succ;
    if (index == size) {
        succ = null;
        pred = last;
    } else {
        succ = node(index);
        pred = succ.prev;
    }

    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o;
        Node<E> newNode = new Node<>(pred, e, null);
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        pred = newNode;
    }

    if (succ == null) {
        last = pred;
    } else {
        pred.next = succ;
        succ.prev = pred;
    }

    size += numNew;
    modCount++;
    return true;
}

2.3 Common methods

  • public E getFirst()

Get the first element of the linked list, throw an exception if the first node does not exist.

public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}
  • public E getLast()

Get the last element of the linked list, throw an exception if the linked list is empty.

public E getLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return l.item;
}
  • public E removeFirst()

Removes the first element, throws an exception if the list is empty.

public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}
  • public E removeLast()

Removes the last element, throws an exception if the list is empty.

public E removeLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}
  • public void clear()

In the case of a linked list, traverse each node and set the internal reference of each node to null, which is convenient for garbage collection.

public void clear() {
    for (Node<E> x = first; x != null; ) {
        Node<E> next = x.next;
        x.item = null;
        x.next = null;
        x.prev = null;
        x = next;
    }
    first = last = null;
    size = 0;
    modCount++;
}
  • public boolean add(E e)

Adds an element to the end of the linked list.

public boolean add(E e) {
    linkLast(e);
    return true;
}
  • public Iterator iterator()

Gets an iterator of the list used to iterate over the elements in the collection.

public Iterator<E> iterator() {
    return new Itr();
}
  • public int size(): Returns the number of elements in the collection.
  • public boolean contains(Object o): Whether to contain an element.
  • public boolean remove(Object o): removes an element.
  • public E get(int index): Get the element with the specified index.
  • public E set(int index, E element): Modify the element value at the specified subscript.
  • public void add(int index, E element): Add an element at the specified subscript.

3 Analysis of common interview questions

3.1 Is LinkedList thread safe?

By analyzing the source code, we can see that any operation on it is not locked, so in a multi-threaded scenario, it is thread-unsafe. It is suitable for non-multi-threaded usage scenarios where there are many additions and deletions.

public static void main(String[] args) throws InterruptedException {

    LinkedList<String> list = new LinkedList<>();

    Thread thread1 = new Thread(() -> {
      for (int i = 0; i < 1000; i++) {
        list.add(Thread.currentThread().getName() + i);
      }
    }, "Thread01");
    thread1.start();

    Thread thread2 = new Thread(() -> {
      for (int i = 0; i < 1000; i++) {
        list.add(Thread.currentThread().getName() + i);
      }
    }, "Thread02");
    thread2.start();

    thread1.join();
    thread2.join();

    System.out.println(list.size()); // 输出不一定是2000,例如1850

}

If there are many additions and deletions, LinkedList can be used, and the addition and deletion of LinkedList is faster.

If you need thread safety, you can use the utility class Collections in JDK collections to provide a method synchronizedList that can turn a thread-unsafe List collection into a thread-safe collection object, as shown below.

public static void main(String[] args) throws InterruptedException {

    LinkedList<String> list = new LinkedList<>();
    // 封装成线程安全的集合
    List<String> synchronizedList = Collections.synchronizedList(list);

    Thread thread1 = new Thread(() -> {
      for (int i = 0; i < 1000; i++) {
        synchronizedList.add(Thread.currentThread().getName() + i);
      }
    }, "Thread01");
    thread1.start();

    Thread thread2 = new Thread(() -> {
      for (int i = 0; i < 1000; i++) {
        synchronizedList.add(Thread.currentThread().getName() + i);
      }
    }, "Thread02");
    thread2.start();

    thread1.join();
    thread2.join();

    System.out.println(synchronizedList.size());

 }

3.2 LinkedList Advantages and Disadvantages

  • Advantages: The speed of addition and deletion operations is fast. Not only does it have double pointers at the head and tail, but it can decide which side to start traversing to find the specified subscript according to which side of the subscript to be operated is close to. Once the location is found, the time complexity of delete and insert operations is O(1).
  • Disadvantages: does not support fast random access, relatively slow compared to ArrayList, but it is not determined, depending on the length of the list, and the index position of the access.

3.3 Can elements be added or deleted during the Iterator process?

Through the source code analysis, in the iterator method of obtaining the collection, the ListItr iterator object defined in the AbstractList abstract class is returned, and the variable expectedModCount is held in its parent class Itr. When the iterator object is initialized, the value of this variable is Give modCount the number of operations in the linked list at this time. When iteratively obtains elements, it will check whether the two variables are equal, and if they are not equal, a concurrent modification exception will be thrown. Therefore, it is not supported to add, delete, or modify the original linked list in the process of using the iterator. However, the addition and deletion operations of the iterator can be called.

private class ListItr extends Itr implements ListIterator<E> {
    ListItr(int index) {
        cursor = index;
    }

    public boolean hasPrevious() {
        return cursor != 0;
    }

    public E previous() {
        checkForComodification();
        try {
            int i = cursor - 1;
            E previous = get(i);
            lastRet = cursor = i;
            return previous;
        } catch (IndexOutOfBoundsException e) {
            checkForComodification();
            throw new NoSuchElementException();
        }
    }

    public int nextIndex() {
        return cursor;
    }

    public int previousIndex() {
        return cursor-1;
    }

    public void set(E e) {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            AbstractList.this.set(lastRet, e);
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    public void add(E e) {
        checkForComodification();

        try {
            int i = cursor;
            AbstractList.this.add(i, e);
            lastRet = -1;
            cursor = i + 1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
}
private class Itr implements Iterator<E> {
    /**
     * Index of element to be returned by subsequent call to next.
     */
    int cursor = 0;

    /**
     * Index of element returned by most recent call to next or
     * previous.  Reset to -1 if this element is deleted by a call
     * to remove.
     */
    int lastRet = -1;

    /**
     * The modCount value that the iterator believes that the backing
     * List should have.  If this expectation is violated, the iterator
     * has detected concurrent modification.
     */
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor != size();
    }

    public E next() {
        checkForComodification();
        try {
            int i = cursor;
            E next = get(i);
            lastRet = i;
            cursor = i + 1;
            return next;
        } catch (IndexOutOfBoundsException e) {
            checkForComodification();
            throw new NoSuchElementException();
        }
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            AbstractList.this.remove(lastRet);
            if (lastRet < cursor)
                cursor--;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException e) {
            throw new ConcurrentModificationException();
        }
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

3.4 Can LinkedList store null values? Can elements be repeated?

The bottom layer of LinkedList is implemented by a doubly linked list, and when adding elements, no value verification is performed on the elements, so null values ​​can be stored, and the stored elements can be repeated.

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

void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

3.5 How to delete specified elements while traversing ArrayList elements?

It is not supported to operate on the original linked list while traversing, and a ConcurrentModificationException will be thrown. We mentioned earlier that when using the iterator to traverse the collection, the collection cannot be added or deleted (which will cause the modCount value to change). The remove method of the Iterator class should be used.

package com.chenpi;

import java.util.Iterator;
import java.util.LinkedList;

/**
 * @author 陈皮
 * @version 1.0
 * @description
 * @date 2022/3/1
 */
public class ChenPi {

  public static void main(String[] args) {

    LinkedList<String> list = new LinkedList<>();
    list.add("Java");
    list.add("C++");
    list.add("Python");
    list.add("Lua");

    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
      String next = iterator.next();
      if ("C++".equals(next)) {
        iterator.remove();
        continue;
      }
      System.out.println(next);
    }

  }
}

// 输出结果如下
Java
Python
Lua

 

Click Follow to learn about HUAWEI CLOUD's new technologies for the first time~

{{o.name}}
{{m.name}}

Guess you like

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