Java ArrayList和LinkedList性能比较

先说结论:

ArrayList LinkedList
表尾插入 时间复杂度O(1) 时间复杂度O(1)
表中插入

平均时间复杂度为O(n/2)。

插入位置越靠近表头时间复杂度越大,最大能达到O(n)。

插入位置越靠近表尾时间复杂度越小,最小能达到O(1)。

但是因为使用了arraycopy赋值数组时间复杂度会小于理论值。

平均时间复杂度为O(n/4)

插入位置越接近中间时间复杂度越大

但是最大也只需O(n/2)

最小为O(1)

表头插入 时间复杂度为O(n) 时间复杂度为O(1)
删除

平均时间复杂度为O(n/2)。

删除目标越靠近表头时间复杂度越大,最大能达到O(n)。

删除目标越靠近表尾时间复杂度越小,最小能达到O(1)。

但是因为使用了arraycopy赋值数组时间复杂度会小于理论值。

平均时间复杂度为O(n/4)

删除越接近中间时间复杂度越大

但是最大也只需O(n/2)

最小为O(1)

修改 时间复杂度O(1) 平均时间复杂度为O(n/4)
根据索引查找 时间复杂度O(1) 平均时间复杂度为O(n/4)

总结:查找和修改偏多使用ArrayList,插入和删除偏多使用LinkedList。此外,时间复杂度都为O(1)时,ArrayList的表现更好。比如在表尾进行插入删除,两者时间复杂度都为O(1),但推荐使用ArrayList。LinkedList涉及到新建对象,效率更低一些。

下面是分析:

插入表尾:

ArrayList:

源码如下,size为数组长度。因此时间复杂度为O(1)。

1
2
3
4
5
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

LinkedList:

源码如下,LinkedList是双向链表,保存有表尾结点last,直接将元素插入到last后面,因此时间复杂度为O(1)。

1
2
3
4
5
6
7
8
9
10
11
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:

源码如下,ArrayList插入数组中需要将后面的所有元素向后移动一个位置,因此平均时间复杂度为O(n/2),由于采用了arraycopy进行数组赋值,时间复杂度会更低一些。

1
2
3
4
5
6
7
8
9
public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

LinkedList:

源码如下,可以看出,将元素插入表中使用的是linkBefore(element, node(index));这行代码。

1
2
3
4
5
6
7
8
public void add(int index, E element) {
        checkPositionIndex(index);

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

我们可以看看node()和linkBefore()这两个方法的代码

node(int index)主要是用来返回排在第index位的结点

可以看出它使用了从两端查找的方式,时间复杂度位O(n/2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
        }
    }

根据源码,我们可以看出linkBefore(E e, Node succ)方法是将我们要插入的结点插到succ前面,可见时间复杂度为O(1)。

1
2
3
4
5
6
7
8
9
10
11
12
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++;
    }

综合上述两个方法来看,LinkedList插入表中,时间复杂度应该为也为O(n/2)。

插入表头:

ArrayList:ArrayList插入表头和插入表中使用的是同一个方法,因此时间复杂度为O(n)

LinkedList:使用的则是addFirst方法,和addLast方法时间复杂度一致,为O(1)。

1
2
3
4
5
6
7
8
9
10
11
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++;
    }

ArrayList的remove源码如下,可见该删除直接将目标元素后面的元素全部往前挪一个位置,从而覆盖掉目标元素。平均时间复杂度为O(n/2)。(size - index - 1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

LinkedList源码如下,使用的是上面提到的node()方法和unlink()方法。时间复杂度主要由node()产生,为O(n/2)。

1
2
3
4
public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

ArrayList使用随机访问,快速定位到index,时间复杂度为O(1)

1
2
3
4
5
6
7
public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

LinkedList需要通过node()方法进行查找目标元素,平均时间复杂度为O(n/4)

1
2
3
4
5
6
7
public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

ArrayList使用随机访问,时间复杂度为O(1)。

1
2
3
4
5
public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

LinkedList需要从两端进行搜索,使用的还是node()方法,平均时间复杂度为O(n/4)。

1
2
3
4
public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }