LinkedList源码分析

数据结构


如图所示,LinkedList 底层是基于双向链表实现的,LinkedList继承于AbstractSequentialList,并且实现了Dequeue接口。
类图:

源码分析

重要成员

    transient int size = 0;
    /**
     * Pointer to first node.
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     */
    transient Node<E> last;

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

add方法

添加到链尾

    /**
     * Appends the specified element to the end of this list.
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    /**
     * 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++;
    } 

可见每次插入都是移动指针,和 ArrayList 的拷贝数组来说效率要高上不少。

添加到链表指定位置


    /**
     * Inserts the specified element at the specified position in this list.
     */
    public void add(int index, E element) {
        checkPositionIndex(index);//检查位置是否合法

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
    /**
     * 插入到指定链表位置
     * 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++;
    }

可见添加到指定位置,需要遍历n/2找到指定位置的元素,然后移动指针。

查询方法

    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
    /**
     * Returns the (non-null) Node at the specified element index.
     */
    Node<E> node(int index) {
        // assert isElementIndex(index);
	// 如果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离链表头比较近,就从节点头部遍历。否则就从节点尾部开始遍历。使用空间(双向链表)来换取时间。

node()会以O(n/2)的性能去获取一个结点。这样的效率是非常低的,特别是当 index 越接近 size 的中间值时。

序列化

LinkedList自定义了序列化方式

  /**
     * Saves the state of this {@code LinkedList} instance to a stream
     * (that is, serializes it).
     */
    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);
    }

    /**
     * Reconstitutes this {@code LinkedList} instance from a stream
     * (that is, deserializes it).
     */
    @SuppressWarnings("unchecked")
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in any hidden serialization magic
        s.defaultReadObject();

        // Read in size
        int size = s.readInt();

        // Read in all elements in the proper order.
        for (int i = 0; i < size; i++)
            linkLast((E)s.readObject());
    }

Deque

LinkedList实现了Deque,而Deque接口定义了在双端队列两端访问、插入、移除和检查元素的方法。

每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。
如下:

FIFO

LinkedList可以作为FIFO(先进先出)的队列,作为FIFO的队列时,下表的方法等价:

队列方法等效方法
add(e)addLast(e)
offer(e)offerLast(e)
remove()removeFirst()
poll()pollFirst()
element()getFirst()
peek()peekFirst()

LIFO

LinkedList可以作为LIFO(后进先出)的栈,作为LIFO的栈时,下表的方法等价:

栈方法等效方法
push(e)addFirst(e)
pop()removeFirst()
peek()peekFirst()

总结

LinkedList 插入,删除都是移动指针效率很高。
查找需要进行遍历查询,效率较低

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×