基于LinkedList1.8源码分析特点

原创
07/01 21:14
阅读数 47
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{}

LinkedList实现了Deque接口:Deque是双端队列的缩写,支持在两端插入和删除元素的线性集合。

    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:LinkedList中的每个元素,记录了与之连接的前后节点。

构造方法

    /**
     * Constructs an empty list.
     */
    public LinkedList() {
    }

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param  c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }

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

首先生成一个空的LinkedList,然后调用addAll方法;此时index == size,这里可以看到首先将传入集合转为数组a,之后遍历数组a,通过Node构造出每个元素的前节点pred和后节点newNode,同时在调用的时候确定LinkedList的first节点和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++;
    }

add方法在集合最后添加元素,即连接最后一个节点;首先取出最后节点last,然后将其作为前节点构造出新的Node节点。之后更新变量last和集合长度。

    public void addFirst(E e) {
        linkFirst(e);
    }

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

addFirst方法直接调用了linkFirst方法。嗯。。。和linkLast相似,取出第一个元素first,将其作为后节点构造出新节点。

    public void addLast(E e) {
        linkLast(e);
    }

addLast方法和add方法相似,都是调用了linkLast方法,区别在于没有返回值。

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

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

当addAll方法在中间插入时(index!=size) ,则首先会调用node方法根据index查找节点:当index靠前时从前向后遍历,否则从后向前遍历。之后遍历数组生成节点,并将所生成节点的最后一个节点和index连接起来。

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

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

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

add方法向中间插入时,可以看到只是查找到index节点,去修改prev和next所指向的节点。

get

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

调用node方法查找元素并返回值。可以知道index靠前或靠后的情况下返回的速度会快一些。

set

    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

查找到index节点,直接修改item值。

remove

    public E remove() {
        return removeFirst();
    }

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

    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 remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

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

移除index个节点:首先找到index位置的节点,将其置为空。这里加了一些判断,保证first和last的可用。

总结

1.修改LinkedList内容实际就是对其中的每个Node节点进行操作,通过next和prev来确定它们的顺序。相比于ArrayList,LinkedList不需要考虑扩容问题;它就是将一个个节点串起来。

2.通过index获得元素效率较低,因为要从集合的两端开始遍历。

3.在中间增删不一定比ArrayList要快:ArrayList可以快速确定操作节点位置但是需要对位置之后的数据进行复制操作。LinkedList可以快速修改节点但是在找到节点位置的时候需要遍历,可以预见的是在首端插入元素是速度是非常快的。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部