• 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 即可了。非常大程度上降低了代码。

  • 相关阅读:
    19.2.15 [LeetCode 80] Remove Duplicates from Sorted Array II
    19.2.15 [LeetCode 79] Word Search
    19.2.15 [LeetCode 78] Subsets
    19.2.15 [LeetCode 77] Combinations
    19.2.15 [LeetCode 76] Minimum Window Substring
    19.2.13 [LeetCode 75] Sort Colors
    19.2.13 [LeetCode 74] Search a 2D Matrix
    19.2.13 [LeetCode 73] Set Matrix Zeroes
    19.2.13 [LeetCode 72] Edit Distance
    19.2.13 [LeetCode 71] Simplify Path
  • 原文地址:https://www.cnblogs.com/claireyuancy/p/7199947.html
Copyright © 2020-2023  润新知