LinkedList 源码学习

本贴最后更新于 1937 天前,其中的信息可能已经时移世异

LinkedList 源码学习

本次分析的代码版本是

JDK 7

一、定义

LinkedList 的内部实现是双向链表,元素和元素之间不是单独存放的,而是通过前节点和后节点连接起来。

节点是 LinkedList 中的一个内部类,具体定义如下。

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

item 指向当前元素,next 指向后节点,prev 指向前节点。

LinkedList 内部主要通过以下三个变量来维护链表关系。

transient int size = 0;
transient Node<E> first;
transient Node<E> last;

二、添加元素

下面是 add 方法的源码。

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

代码的逻辑是这样的:

  1. 通过 Node 的构造器方法创建一个新的节点 newNode,将现在的最后一个元素 l 作为新节点 newNode 的前节点,当前元素就是自己 e,后节点为 null
  2. 将尾节点 last 指向新节点 newNode
  3. 如果原来的尾结点 lnull,说明添加的是第一个元素,将新节点作为头结点 first。如果尾结点 l 不为 null,将原来的尾结点 l 的后节点 l.next 指向新节点 newNode
  4. 容量 size 加 1,容器结构性变化次数 modCount 加 1。

三、根据索引查找元素

下面是 get 方法的源码。

public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}

private void checkElementIndex(int index) {
    if (!isElementIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

private boolean isElementIndex(int index) {
    return index >= 0 && index < size;
}

Node<E> node(int index) {
    // assert isElementIndex(index);

    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;
    }
}
  1. 先检查索引 index 是否合法,如果不合法就抛出索引越界异常 IndexOutOfBoundsException
  2. size >> 1 就是 size 除以 2,判断被查找元素的索引在前半部分还是在后半部分,如果在前半部分就从头查找,否则从尾部开始查找。

四、根据内容查找元素

public int indexOf(Object o) {
    int index = 0;
    if (o == null) {
        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;
}

1.判断元素是否为 null,如果为 null ,则将找到的第一个为 null 的元素返回,否则使用 equals 方法从头到尾查询,将相等的第一个元素返回。

可以看出查找元素的性能是比较差的。

五、插入元素

public void add(int index, E element) {
    checkPositionIndex(index);

    if (index == size)
        linkLast(element);
    else
        linkBefore(element, node(index));
}

private void checkPositionIndex(int index) {
    if (!isPositionIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

private boolean isPositionIndex(int index) {
    return index >= 0 && index <= size;
}

void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    final Node<E> pred = succ.prev;
    final Node<E> newNode = new Node<>(pred, e, succ);
    succ.prev = newNode;
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}
  1. 检查索引是否合法,如果不合法就抛出索引越界异常 IndexOutOfBoundsException
  2. 如果 index 就是容量 size 的大小,直接调用 linkLast 在链表末尾添加元素。
  3. 如果 index 小于容量 size,就调用 linkBefore 插入元素。
  4. linkBefore 方法中,参数 e 代表需要插入的元素,参数 succ 代表需要插入的索引位置对应的元素。
  5. 新建一个节点 newNode, 前节点为 pred,后节点为 succ
  6. 让后节点的上一个节点 succ.prev 指向新节点 newNode
  7. 让前节点的下一个节点指向新节点,如果前节点为空,让头节点 first 指向新节点 newNode
  8. 容量 size 加 1,容器结构性变化次数 modCount 加 1。

六、删除元素

public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}

private void checkElementIndex(int index) {
    if (!isElementIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

private boolean isElementIndex(int index) {
    return index >= 0 && index < size;
}

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;

    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }

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

    x.item = null;
    size--;
    modCount++;
    return element;
}

删除主要调用了 unlink 方法,删除的主要思路就是让被删除元素 x 的前节点和后节点连接起来。

  1. x 的前节点的下一个节点指向 x 的后节点,如果 x 的前节点为空,说明删除的是头节点,需要将 x 的后节点作为头节点。
  2. x 的后节点的上一个节点指向 x 的前节点,如如果 x 的后节点为空,说明删除的尾节点,需要将 x 的前节点作为尾节点。
  3. 容量 size 减 1,容器结构性变化次数 modCount 减 1。

(完)

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3187 引用 • 8213 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...