昨天做题时,看到了Collections的reverse方法,区分了arraylist和linkedlist的情况,一时兴起,看了看源码,发现了一些有趣的情况。
情况一 尾插
public static void main(String[] args){
List list1 =new ArrayList();
long t1 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
list1.add(i);
}
long t2 = System.currentTimeMillis();
System.out.println(t2-t1);
List list2 =new LinkedList();
long t3 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
list2.add(i);
}
long t4 = System.currentTimeMillis();
System.out.println(t4-t3);
}
结果
情况二 中部插入
public static void main(String[] args){
List list1 =new ArrayList();
long t1 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
list1.add(i/2,i);
}
long t2 = System.currentTimeMillis();
System.out.println(t2-t1);
List list2 =new LinkedList();
long t3 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
list2.add(i/2,i);
}
long t4 = System.currentTimeMillis();
System.out.println(t4-t3);
}
情况三 头插
public static void main(String[] args){
List list1 =new ArrayList();
long t1 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
list1.add(0,i);
}
long t2 = System.currentTimeMillis();
System.out.println(t2-t1);
List list2 =new LinkedList();
long t3 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
list2.add(0,i);
}
long t4 = System.currentTimeMillis();
System.out.println(t4-t3);
}
要解释上述情况,就要从这arraylist和linkedlist的add方法说起了
先看arraylist的
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index); //检查是否有异常
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index); //复制数组,复制长度为size-index
elementData[index] = element;
size++;
}
所以,在进行尾插的时候,执行的是第一个add方法,数组扩容,在尾部插入即可,只需6ms
中部插入,每插入一个元素,要复制一半的数组内容,所以是320ms
头插,每插入一个元素,要复制整个数组的内容,所以需要726ms(这几个时间只是大致值,每次输出并不完全相同)
再看linkedlist的
public boolean add(E e) {
linkLast(e);
return true;
}
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++;
}
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
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++;
}
从代码中可以看出,头插(prev==null)和尾插(index==size)时,都是在链表端部直接插入,无需遍历,所以用时很少,
而中部插入时,每次都要遍历一半的链表,才能进行插入,自然耗时很多了
但是又引发了一个新的问题,到底谁的插入效率更高呢?
直接上结果
在开始时,两者相差不多,十万条数据时,linkedlist显示出优势,但百万条数据时,arraylist比linkedlist用时少了很多,千万数据时,linkedlist又反超了,结果很是奇异~
翻了下arraylist的扩容原理,默认10,每次扩容是之前容量的1.5倍
而linkedlist只是添加元素
查了一下 有一种说法:linkedlist是每一次都需要去new新对象,修改与链表之间的相互引用。一次性分配内存总是会比多次分配内存花费的时间少,可以解释arraylist第一次“超车”,但无法解释千万数据级的情况,唔,这个问题只能先搁置于此了