Java集合之LinkedList详解

简介

LinkedList是基于双向链表实现的,LinkedList同样是非线程安全的,只在单线程下适合使用。

LinkedList类声明如下:

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

它继承于AbstractSequentialList,实现了List、Deque、Cloneable、 Serializable等接口。

LinkedList实现了List接口,可以对它进行队列操作;实现了Deque接口,即能将LinkedList当作双端队列使用;实现了Cloneable接口,能被克隆;实现了Serializable接口,因此它支持序列化,能够通过序列化传输。

LinkedList源码详解

LinkedList有两个构造方法:

// 默认构造方法
public LinkedList() { }
// 根据其他集合创建LinkedList
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

LinkedList类中的元素节点都是Node类型的:

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;
    }
}

我们下面主要来看一看LinkedList的add和remove方法。

add方法

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

add(E)方法很简单,其中,添加元素是通过linkLast(E)方法来实现的:

void linkLast(E e) {
    // 获取尾节点
    final Node<E> l = last;
    // 创建新节点,新节点的前驱节点是l,后继节点是null
    final Node<E> newNode = new Node<>(l, e, null);
    // 更新尾节点
    last = newNode;
    // 若原始链表为空,则初始化首节点
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    // 更新元素个数
    size++;
    modCount++;
}

remove方法

remove方法有两个重载方法:

// 删除元素
public boolean remove(Object o)
// 删除index下标对应元素
public E remove(int index)

remove(Object)方法比较简单,我们先来看这个方法:

public boolean remove(Object o) {
    // 要删除的元素为null
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } 
    // 要删除的元素不为null
    else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}

LinkedList删除元素时,是分为元素为null和不为null两种方式来判断的,这也说明LinkedList允许添加null元素;同时,如果这个元素在LinkedList中存在多个,则只会删除最先出现的那个。

删除元素,采用了unlink(E)方法来进行删除:

E unlink(Node<E> x) {
    // assert x != null;
    // 获取要删除的元素值
    final E element = x.item;
    // 获取元素后继节点
    final Node<E> next = x.next;
    // 获取元素前驱节点
    final Node<E> prev = x.prev;

    // 若前驱节点为null,则更新首节点
    if (prev == null) {
        first = next;
    } 
    // 否则,更新前驱节点的后继节点
    else {
        prev.next = next;
        x.prev = null;
    }

    // 若后继节点为null,则更新尾节点
    if (next == null) {
        last = prev;
    } 
    // 否则,更新后继节点的前驱节点
    else {
        next.prev = prev;
        x.next = null;
    }

    // 一些清除工作
    x.item = null;
    // 减少元素个数值
    size--;
    modCount++;
    return element;
}

remove(int)方法源码如下:

public E remove(int index) {
    // 检查index下标的合法性
    checkElementIndex(index);
    // 获取元素值,并通过unlink方法删除节点
    return unlink(node(index));
}

remove(int)方法会先检查index下标的合法性,然后通过node(int)方法获取对应元素节点,删除节点同样是通过unlink(E)方法,checkElementIndex(int)方法源码如下:

private void checkElementIndex(int index) {
    // 若index下标不合法,则抛出IndexOutOfBoundsException异常
    if (!isElementIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

// 判断index下标是否合法
private boolean isElementIndex(int index) {
    return index >= 0 && index < size;
}

node(int)方法源码如下:

Node<E> node(int index) {
    // assert isElementIndex(index);
    // 若index小于size >> 1,通过从首节点向后遍历来寻找节点
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } 
    // 否则,通过从尾节点向前遍历来寻找节点
    else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

该方法返回双向链表中指定下标的节点,而链表中是没有下标索引的,要找出指定位置的元素,就要遍历该链表,从源码的实现中,我们看到这里有一个加速动作。源码中先将index与size的一半比较,如果index < (size >> 1),就只从首节点向后遍历来寻找节点,否则,就从尾节点向前遍历来寻找节点。这样可以减少一部分不必要的遍历,从而提高一定的效率(实际上效率还是很低)。

其他相关方法

get(int)方法
public E get(int index) {
    // 检查index下标的合法性
    checkElementIndex(index);
    // 获取元素值
    return node(index).item;
}

get(int)方法会先检查index下标的合法性,然后通过node(int)方法获取对应元素节点。

clear()方法
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
    for (Node<E> x = first; x != null; ) {
        Node<E> next = x.next;
        // 将元素值置为null
        x.item = null;
        // 将next置为null
        x.next = null;
        // 将prev置为null
        x.prev = null;
        x = next;
    }
    // 将首尾节点置为null
    first = last = null;
    size = 0;
    modCount++;
}

clear()方法将链表元素置空,帮助GC来释放节点占用的内存。

LinkedList还实现了栈和队列的操作方法,因此也可以作为栈、队列和双端队列来使用,如push、pop、addFirst、addLast、offerFirst、offerLast等方法。这里不再介绍,实现都比较简单,有兴趣的可以看源码。

LinkedList和ArrayList的区别

  • ArrayList是基于动态数组实现的数据结构,LinkedList是基于双向链表实现的数据结构。
  • 对于随机访问操作get和set,ArrayList要优于LinkedList,因为LinkedList要移动指针找index对应的元素。
  • 对于添加和删除操作add和remove,LinedList比较占优势,因为除了要找index对应的元素,ArrayList还要移动数据。

所以,ArrayList更适合用于读操作多的场景,linkedList更适合用于添加或删除数据频繁的场景。

相关博客

Java集合之ArrayList详解

猜你喜欢

转载自blog.csdn.net/qq_38293564/article/details/80664386