title: ArrayList与LinkedList categories:
- 数据结构
##1. 关系图
还是只放一个UML图吧,蓝色的箭头是继承关系,绿色虚线箭头是实现的接口
##2. 数组与链表的区别
在内存上,数组是连续地址,而链表是离散地址;数组通过下标定位,而链表只能通过指针定位;
||get(index)|contain(obj)|add/remove(index,obj)|空间|| |: |ArrayList |O(1)| O(N)|O(N) | O(N)| |LinkedList | O(N) | O(N) |O(1)* |O(N)
- 这里的get可以用“定位/index”翻译,而contain可以翻译为“查找/Search”,两者翻译不可混为一谈;
- add/remove这里有争议,特指已经查找到了元素位置的情况下,移动数据所需要的时间;
- 不考虑第一与最后位的特殊情况
####1. 定位(get(index))
数组通过下标进行定位
E elementData(int index) {
return (E) elementData[index];
}
链表需要遍历整个节点
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;
}
}
####2. 搜索(contain(obj))
在数组中,contain实际上就是indexof,因为需要遍历查表对比,所以复杂度都是O(N)
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
在链表中,是通过指针一步步遍历的
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
####3. 添加或删除(add/remove(index,obj))
在数组中,插入元素需要调整后面的所有元素,所以消耗比较多,我们以add为例,可以看出,是先复制后添加的,复杂度为O(N);
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
//使用native代码以提高效率
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
而在链表中,add时间为{定位时间 + O(1)},我们平时说的添加删除的复杂度O(1)是指已经获得当前定位的情况,在stackoverFlow上也有关于这个的讨论,你要说到底我支不支持O(1)呢,那我当然支持了,毕竟都是那么写的嘛
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
//这里node()定位的复杂度已经是O(N)了,但是我们不考虑
linkBefore(element, node(index));
}
/**
* Inserts element e before non-null Node succ.
* 这里的元素数据复杂度为O(1)
*/
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++;
}
4. 总结
总得来说,就是Array定位比LinkedList快,而插入删除(不包括定位时间的情况)操作LinkedList更快。另外再说一下,比如Android的ListView场景中很少存在插入数据的情况,大多数情况就是在末尾添加数据,所以没必要为了这点性能而放弃ArrayList。