先说结论:
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)。
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
LinkedList:
源码如下,LinkedList是双向链表,保存有表尾结点last,直接将元素插入到last后面,因此时间复杂度为O(1)。
void linkLast(E e) { final Node
l = last; final Node 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进行数组赋值,时间复杂度会更低一些。
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));这行代码。
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)
Node
node(int index) { // assert isElementIndex(index); if (index < (size >> 1)) { Node x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } } 根据源码,我们可以看出linkBefore(E e, Node
succ)方法是将我们要插入的结点插到succ前面,可见时间复杂度为O(1)。 void linkBefore(E e, Node
succ) { // assert succ != null; final Node pred = succ.prev; final Node 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)。
private void linkFirst(E e) { final Node
f = first; final Node 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)
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)。
public E remove(int index) { checkElementIndex(index); return unlink(node(index)); }
ArrayList使用随机访问,快速定位到index,时间复杂度为O(1)
public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
LinkedList需要通过node()方法进行查找目标元素,平均时间复杂度为O(n/4)
public E set(int index, E element) { checkElementIndex(index); Node
x = node(index); E oldVal = x.item; x.item = element; return oldVal; }
ArrayList使用随机访问,时间复杂度为O(1)。
public E get(int index) { rangeCheck(index); return elementData(index); }
LinkedList需要从两端进行搜索,使用的还是node()方法,平均时间复杂度为O(n/4)。
public E get(int index) { checkElementIndex(index); return node(index).item; }