先说结论:
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;
}