jdk源码分析之LinkedList

LinkedList关键属性

size表示当前链表保存了多少数据,first指针指向链表第一个数据,last指针指向链表最后一个数据

    transient int size = 0;
    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

LinkedList底层数据结构

Linked是一个双向链表,链表节点的数据如下,有一个数据域和两个指针域

    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首尾添加数据linkFirst(E e)和linkLast(E e)

    /**
     * Links e as last element.
     */
    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++;
    }

先保存旧链表的尾指针为l,然后根据传入的数据e和为指针l构建一个数据节点。

newNode = new Node<>(l, e, null)

将链表的尾节点修改为新创建的节点newNode。如果l==null,表示之前链表里边没有保存数据,现在添加了一个新数据,头指针和尾指针都应该指向这个新添加的节点

        last = newNode;
        if (l == null)
            first = newNode;

如果之前链表里边保存的有数据,l!=null,则修改新节点链接到原链表的尾部

l.next = newNode;

然后修改size和modCount(迭代器并发访问用到的属性)
linkFirst(E e)代码类似,原理一样

在LinkedList的某一节点前插入数据linkBefore

    /**
     * Inserts element e before non-null Node succ.
     */
    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++;
    }

在节点succ前插入数据 e,则succ是e的后继节点,e的前序就是succ的前序,因此根据e、前序、后继构造一个新的节点newNode = new Node<>(pred, e, succ),修改succ的前序节点为新节点newNode,succ.prev = newNode,然后再将断开的链表链接起来

LinkedList删除头结点unlinkFirst和尾节点unlinkLast

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

首先保存头结点的后继节点指针(即将成为新的头结点),然后头结点指针域和数据域置null,加快内存回收速度,然后判断next是否为null,null的话表示原链表只有一个节点,现在还把唯一的节点删除了,因此first头指针和last尾指针都应该为null。next不为null的话表示删除头结点后链表还有数据,修改next的prev指针为null即可(pre指针为null表示此接节点为队首数据,next指针为null表示队尾数据)
unlinkLast(Node l) 代码和原理类似

LinkedList删除某一节点unlink

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

仍然是指针操作,提取待删除节点的数据域、前序和后继,数据域用于返回值,前序和后继用于链表的断连操作
前序为null表示带删除节点是队首节点,修改first指针为待删除节点的后续节点
前序节点不为null就把前序节点的next指针指向后继节点,待删除节点的prev指针域置null,这样待删除节点的前序节点指针已经断开重连了,然后处理后继节点指针的断开重连
判断后继节点是否为null,是null的话表示待删除节点就是队尾,修改队尾指针last为待删除节点的前序
后继节点不为null,后继节点的prev指针指向待删除节点的前序节点,待删除节点的next指针置null,这样待删除节点的后继结点的指针也已经断开重连了
然后把待删除节点的数据域置null,修改size和modCount,返回删除节点的数据

LinkedList的随机访问node(int index)

  /**
     * Returns the (non-null) Node at the specified element 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;
        }
    }

链表插入删除添加数据比较方便,直接修改指针的断连即可。但是链表并不能够像数组那样随机访问,只能进行遍历。LinkedList是一个双向链表,可以对遍历做一个小优化,只遍历半个链表即可。
如果位置在链表前半部分index < (size >> 1),正向遍历查找for (int i = 0; i < index; i++)
否则逆向遍历查找for (int i = size - 1; i > index; i–)

LinkedList的某一位置添加一个集合addAll

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

首先检查插入位置是否合法

    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }

可见插入位置区间为【0,size】,可以队首插入,可以队尾插入,中间任意位置当然也可以
然后把集合对象转化为数组,数组长度为0表示集合没有数据,直接返回false表示插入失败
构建插入节点的前序和后继,如果插入位置index==size的话表示队尾插入数据,因此后续为null,前序为队尾last

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

不是在队尾插入集合的话需要根据插入位置index找到后继节点,找到了后继也就找到了前序

        else {
            succ = node(index);
            pred = succ.prev;
        }

Node node(int index)方法用于返回特定位置的节点,这样插入点的前序和后继都找到了,开始循环把集合的数据添加到链表的特定位置

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

通过new Node<>(pred, e, null)创建新节点,前序指针在创建的时候在构造函数里边已经指明。
pred == null表示是在队首添加数据,需要修改队首指针first
否则把前序节点的后继指针设置为newNode
再把前序节点设为newNode,接着下一次循环
这样在循环结束后,只有最后一个添加的节点的后继指针没有处理

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

如果succ后继为null,修改队尾指针为last为最后一个添加的节点pred
否则设置最有一个添加的节点的后继指针为succ,succ的前序指针指向最后一个添加的节点pred
最后修改size和modCount,返回true表示添加成功

LinkedList的双端队列特性

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

LinkedList实现了Deque接口,因此可以吧LinkedList当做一个双端队列属性,比如peek和poll,均是通过链表的基本操作实现相应的功能,其他的方法类似,不再一一列举。

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

猜你喜欢

转载自blog.csdn.net/shihui512/article/details/51480221