Java基础:数据容器源码分析之LinkedList      
View on GitHub

龙珠

修炼自己与发现世界

Java基础:数据容器源码分析之LinkedList

By arthur503 -- 10 Oct 2013

对数据容器的源码分析,先分析的LinkedList,因为之前对于Java中没有指针但还可以保存链表不太理解,因此来看看如何链接的。

看完之后,觉得主要有以下几点需要注意的:

在LinkedList中,基本操作包括:创建、查找、获取和设置、添加(追加和插入)和删除(特定内容元素和特定位置元素)。在源代码中,表征位置的变量包括:first、last、prev、next。

一、概述

LinkedList本身是一个双向链表结构,包含的元素为Node。Node的定义在LinkedList内部,代码为:

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内部包含本身变量item,以及前后两个节点Node。形式上相当于C++中的指针,Java中没有指针的概念,此处为引用。

LinkedList中的双向链表为环状结构。在为空时,header的前后引用均为本身。在按照元素位置进行查找或添加元素时,可以利用这一点来提高效率。

LinkedList继承自AbstractSequentialList(继承自AbstractList),实现的接口包括:List, Deque, Cloneable, java.io.Serializable。其中,AbstractSequentialList继承自AbstractList,已经实现了List接口,放在此处只是更加明了。

LinkedList本身不是线程安全的,因此,在多线程中使用时必须使用外部方法同步。

LinkedList中的基本操作包括:创建、查找、获取、设置、添加、删除。我们分别来分析。

二、创建

LinkedList的创建包括两个构造方法:无参数和Collection为参数。如下:

public LinkedList() {
}

public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

可以看出,Collection为参数的方法,首先调用无参构造方法,然后调用添加Collection的方法来初始化。

三、查找(时间复杂度为O(n))

LinkedList中,查找某元素有两种形式:通过index查找和通过内容查找。

通过对象的index查找的方法为node(),代码如下:

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/2对比,分成从前往后和从后往前查找,时间复杂度降低为O(n/2)。

通过对象的内容查找包括,indexOf()、lastIndexOf()方法,我们查看indexOf()代码如下:

public int indexOf(Object o) {
    int index = 0;
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;
}

此时,时间复杂度就是O(n)了。

四、获取和设置

获取和设置操作包括get()、set()方法,他们都依赖于查找操作,因此时间复杂度为O(n)。代码如下:

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

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

五、添加和删除

LinkedList中添加元素操作有两类:追加和插入。

添加操作涉及的方法有:add(), addAll(), addFirst(), addLast()。核心操作包括:linkFirst(), linkBefore().

追加操作相当于linkLast(),操作时间复杂度为O(1);对特定位置index的插入操作,如果index与size相同,则相当于追加操作,否则需要查找到位置后再linkBefore()操作(时间复杂度为O(n)),插入操作总的时间复杂度为O(n)。代码如下:

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

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

LinkedList中删除元素操作有两类:删除特定内容元素和删除特定位置元素。其核心操作为unlink(),代码如下:

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

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

可以看出,两种方法由于都需要寻找元素,所以时间复杂度均为O(n)。

注意:对于remove(Object o)方法,如果Object为Integer元素,必须把参数强制转化为Integer,否则按照int处理从而调用remove(int index)方法;

此处小结一下:添加和删除操作中,只有追加的时间复杂度是O(1),其他如插入到特定位置、删除特定元素、删除特定位置元素的操作,由于需要寻找位置,时间复杂度都是O(n)。

参考资料: