美文网首页
LinkedList源码浅析

LinkedList源码浅析

作者: hdychi | 来源:发表于2019-05-08 15:19 被阅读0次

一、简介

面试时候的一大经典问题:你知道ArrayList和LinkedList的区别吗?(不,我不知道)。基本上看过面经的都知道,ArrayList是数组结构,LinkedList是链表结构。但如果这时候再问,它们的源码有读过吗?凉凉夜色为你思念成河~
LinkedList存储数据确实是链表结构,其数据存储字段为:

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    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;
....
}

first即为链表的头节点,last的为尾节点。Node中,有记录前驱节点和后继节点,以及数据item:

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存储数据使用的是双向链表数据结构。在接下来的部分,我们再来看一下LinkedList的查询、增加和删除方法,即get、add、remove方法。

二、get方法

get方法传入的是index,即第几个元素(下标从0开始)。虽然这是链表结构吧,但get方法的参数与ArrayList类似。

/**
     * Returns the element at the specified position in this list.
     *
     * @param index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

检查一下index是否超过了size,然后返回node(index)的item。node方法如下:

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

可以看到,如果index小于size>>1,即要查找的元素在链表前半部分,则从first节点往后查找,否则从last节点往前查找。

三、 add方法

add方法就主要看add(index,element)这个方法,毕竟这也包括了add(element)的情况,即添加在链表末尾的情况。

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

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

1.检查一下index是否合法
2.如果index==size即要插入到链表末尾

    /**
     * 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,element为e,后继节点为null的节点,并且修改原尾指针的后继节点,再将尾指针指向插入的节点。
3.如果index < size,插入到链表中间,调用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++;
    }

传入的参数为要插入的元素e和后继节点succ。插入逻辑也挺简单,先得到后继节点的前驱节点pred,创建前驱节点为pred,后继节点为succ的新节点,再修改前驱的后继和后继的前驱即可。

四、remove方法

移除的话主要看remove(int index)和remove(Object o)方法。
移除对象的时候,就是在链表中找到o,然后调用unlink方法:

    public boolean remove(Object o) {
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

remove(index)也是类似,找到对象后调用unlink方法:

public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

接下来看看殊途同归的unlink方法:

/**
     * Unlinks non-null node x.
     */
    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;
    }

其实就是先看看要删除的节点的前驱和后继,修改前驱的next为指针为后继,后继的prev指针为前驱,碰到头节点或尾节点的情况分类讨论即可。

五、与ArrayList对比

1.ArrayList中存在扩容,在扩容时还有拷贝操作,而LinkedList没有扩容。
2.ArrayList中增加删除元素时,都得调用System.arraycopy,即拷贝操作,而LinkedList主要得先找到插入/删除的节点。不清楚System.arraycopy的源码和性能,笔者也不知道怎么比较,反正平均复杂度应该都是O(n)的。LinkedList要插入/删除的元素在链表中间而链表又长时,应该是比较慢的。
3.ArrayList的查找是O(1)的,LinkedList的查找是O(n)的。
(ps:LinkedList的增删查比ArrayList好懂多了,面试的时候如果要说源码可以说LinkedList)

相关文章

网友评论

      本文标题:LinkedList源码浅析

      本文链接:https://www.haomeiwen.com/subject/tommoqtx.html