MST

星途 面试题库

面试题:Java集合框架中List性能对比及源码解析

对比Java中ArrayList和LinkedList在随机访问、插入和删除操作上的性能差异,并结合它们的源码来解释造成这些差异的原因。
19.1万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

随机访问性能差异

  • ArrayList:随机访问性能高。因为ArrayList基于数组实现,在内存中是连续存储的,可以通过数组下标直接定位元素,时间复杂度为O(1)。源码中,获取元素通过elementData[index]直接访问数组指定位置元素。例如:
public E get(int index) {
    rangeCheck(index);
    return elementData(index);
}
E elementData(int index) {
    return (E) elementData[index];
}
  • LinkedList:随机访问性能低。LinkedList是链表结构,访问元素时需要从链表头或尾开始遍历,直到找到目标元素,时间复杂度为O(n)。源码中,获取元素时会先判断index靠近头还是尾,然后从相应位置开始遍历查找,如:
public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
}
Node<E> node(int 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;
    }
}

插入和删除操作性能差异

  • ArrayList:在中间或开头插入、删除元素性能低。因为需要移动元素来维持数组的连续性,时间复杂度为O(n)。在尾部插入删除性能较好,时间复杂度为O(1)(不考虑扩容情况)。源码中,插入元素时,如果插入位置不是尾部,需要调用System.arraycopy方法移动元素,如:
public void add(int index, E element) {
    rangeCheckForAdd(index);
    ensureCapacityInternal(size + 1); 
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    elementData[index] = element;
    size++;
}

删除元素类似,也需要移动元素:

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; 
    return oldValue;
}
  • LinkedList:在中间或开头插入、删除元素性能高。因为只需修改链表节点的指针,时间复杂度为O(1)。在尾部插入删除性能也较好,时间复杂度为O(1)。源码中,插入元素时,只需修改新节点和相邻节点的指针,如:
void linkBefore(E e, Node<E> succ) {
    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 unlink(Node<E> x) {
    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;
}