OpenJDK 源码阅读之 LinkedList

概要

  • 类继承关系
java.lang.Object
    java.util.AbstractCollection<E>
        java.util.AbstractList<E>
            java.util.AbstractSequentialList<E>
                java.util.LinkedList<E>
  • 定义
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
}
  • 要点
  1. 双链表
  2. 非同步
  3. fail-fast: 即创建 iterator 后,对链表进行了添加,删除,但是却没有用 iterator 的方法,就会抛出 ConcurrentModificationException 异常。

实现

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

双链表每个结点由 Node 类表示, 由其成员可以看出,确实是一个双链表。

  • add
/**
 * Appends the specified element to the end of this list.
 *
 * <p>This method is equivalent to {@link #addLast}.
 *
 * @param e element to be appended to this list
 * @return {@code true} (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    linkLast(e);
    return true;
}

add 操作会在表的最后添加一个元素,调用 linkLast 实现。

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

linkLast 先生成新的结点,再将原来的最后结点的 next 指向新结点,需要注意链表为空时的情况。另外,新结点的 prev,next 都是在其构造函数中设置的,所以需要将其相邻结点传入。构造函数为:

   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;
        }
    }
  • serialVersionUID
    private static final long serialVersionUID = 876323262645176354L;

序列化版本号,如果前一版本序列化后,后一版本发生了很大改变,就使用这个号告诉虚拟机,不能反序列化了。

  • writeObject

比例一下 ArrayList 与 LinkedList 中的
writeObject

    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out array length
        s.writeInt(elementData.length);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++)
            s.writeObject(elementData[i]);

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

    }
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // Write out any hidden serialization magic
        s.defaultWriteObject();

        // Write out size
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (Node<E> x = first; x != null; x = x.next)
            s.writeObject(x.item);
    }

注意后者没有检查 modCount,这是为什么呢?之前看 ArrayList的时候觉得是为线程安全考虑的,可是现在为什么又不检查了呢?虽然两个文件的注释中都说到,如果有多个线程操作此数据结构,应该从外部进行同步。但是一个检查,一个不检查是几个意思呀?

  • 维护数据结构一致性
    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

注意代码第 5行对 f 的检查,6 行对 last 的调整。一定要细心,保证操作后, 所有可能 涉及的数据都得到相应更新。

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

注意返回的是数据,而不是Node,外部根本不需要知道 Node 的存在。
另外,为什么 f == null 要抛出异常而不是返回null

  • 为什么要分成两个函数 
    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

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

删除操作分成两个函数,这是为什么呢?还有其它的一些操作也是这样。能想到的是其它操作可能也需要用到 unlinkFirst

  • LinkedList 中以 index 检索
   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;
        }
    }

可以看出这里的小技巧,以 index 在前半段还是后半段,来决定是从前向后搜索,还是从后向前。

  • 代码重复问题
    public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }

    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

好奇这两个函数为什么会同时存在,Google到,原来是为了实现不同的接口,所以需要同时存在这两个函数,类似的情况还存在。

  • DescendingIterator
   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();
        }
    }

这个很有意思,直接把 nexthasNext 函数设置为 previous 就行了,很大程度上减少了代码。

OpenJDK 源码阅读之 LinkedList,布布扣,bubuko.com

时间: 2024-10-08 16:25:50

OpenJDK 源码阅读之 LinkedList的相关文章

OpenJDK 源码阅读之 ArrayDeque

概要 类继承关系 java.lang.Object java.util.AbstractCollection<E> java.util.ArrayDeque<E> 定义 public class ArrayDeque<E> extends AbstractCollection<E> implements Deque<E>, Cloneable, Serializable 要点 对 Deque 接口的实现 可调整大小 非线程安全 作为栈比 Stac

OpenJDK 源码阅读之 TimSort

概要 这个类在 Oracle 的官方文档里是查不到的,但是确实在 OpenJDK 的源代码里出现了,Arrays 中的 sort 函数用到了这个用于排序的类.它将归并排序(merge sort) 与插入排序(insertion sort) 结合,并进行了一些优化.对于已经部分排序的数组,时间复杂度远低于 O(n log(n)),最好可达 O(n),对于随机排序的数组,时间复杂度为 O(nlog(n)),平均时间复杂度 O(nlog(n)).强烈建议在看此文前观看 Youtube 上的 可视化Ti

OpenJDK 源码阅读之 Arrays

概要 类继承关系 java.lang.Object java.util.Arrays 定义 public class Arrays extends Object 要点 此类主要是提供了一些操作数组的方法,比如排序啊,搜索啊.也提供一个工厂,用于将数组当成一个 List. 实现 quick sort public static void sort(int[] a) { DualPivotQuicksort.sort(a); } sort 使用了 util 中的另一个类中的方法,DualPivotQ

OpenJDK 源码阅读之 ArrayList

概要 类继承关系 java.lang.Object java.util.AbstractCollection<E> java.util.AbstractList<E> java.util.ArrayList<E> 定义 public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serial

OpenJDK 源码阅读之 Java 字节流输入类的实现

Java 的输入输出总是给人一种很混乱的感觉,要想把这个问题搞清楚,必须对各种与输入输出相关的类之间的关系有所了解.只有你了解了他们之间的关系,知道设计这个类的目的是什么,才能更从容的使用他们. 我们先对 Java I/O 的总体结构进行一个总结,再通过分析源代码,给出把每个类的关键功能是如何实现的. Java I/O 的主要结构 Java 的输入输出,主要分为以下几个部分: 字节流 字符流 Socket 新 I/O 每个部分,都包含了输入和输出两部分. 实现概要 这里只给出每个类的实现概要,具

OpenJDK 源码阅读之 Java 字节流输出类的实现

Java 的输入输出总是给人一种很混乱的感觉,要想把这个问题搞清楚,必须对各种与输入输出相关的类之间的关系有所了解.只有你了解了他们之间的关系,知道设计这个类的目的是什么,才能更从容的使用他们. 这是这个系列的第二篇,描述字节输出类的实现,第一篇见:OpenJDK 源码阅读之 Java 字节流输入类的实现 字节流输出 图1 Java 字节输出类 OutputStream OutputStream是所有字节输出类的超类,这是个抽象类,需要实现其中定义的 write 函数,才能有实用的功能. pub

转-OpenJDK源码阅读导航跟编译

OpenJDK源码阅读导航 OpenJDK源码阅读导航 博客分类: Virtual Machine HotSpot VM Java OpenJDK openjdk 这是链接帖.主体内容都在各链接中. 怕放草稿箱里过会儿又坑掉了,总之先发出来再说…回头再慢慢补充内容. 先把ItEye网站上的信息聚合起来. 近期提问帖: 阅读openjdk源代码 如何来看OpenJDK源码 如何分析OpenJDK中JVM的实现 一个个回复太麻烦了,合在一块儿写这么一篇. ================ 前言 我的

openjdk源码阅读导航

转自:http://rednaxelafx.iteye.com/blog/1549577 这是链接帖.主体内容都在各链接中. 怕放草稿箱里过会儿又坑掉了,总之先发出来再说…回头再慢慢补充内容. 先把ItEye网站上的信息聚合起来. 近期提问帖: 阅读openjdk源代码 如何来看OpenJDK源码 如何分析OpenJDK中JVM的实现 一个个回复太麻烦了,合在一块儿写这么一篇. ================ 前言 我的VM帖的索引 高级语言虚拟机(HLLVM)群组 新浪微群“JVM源码阅读活

java1.7集合源码阅读:LinkedList

先看看类定义: 1 public class LinkedList<E> 2 extends AbstractSequentialList<E> 3 implements List<E>, Deque<E>, Cloneable, java.io.Serializable 4 { 5 ....... 6 } LinkedList与ArrayList相比,LinkedList不再实现RandomAccess接口,表明不支持快速随机访问数据,但实现了Deque接