面试题答案
一键面试随机访问性能差异
- 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;
}