一、疑问产生
ArrayList 是一个可改变大小的数组.当更多的元素加入到ArrayList中时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问,因为ArrayList本质上就是一个数组
LinkedList 是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.
乍一看好像没啥问题,那么,写代码测试咯
1,000次的add、get、remove ArrayList 与 LinkedList 对比
以下为三次输出,数字代表耗费时间,单位为纳秒
Test times = 1,000
----------------------------------------------------------------------------------------------------
1153728 <--ArrayList add 1170948 <--ArrayList add 1213081 <--ArrayList add
681832 <--LinkedList add 655819 <--LinkedList add 673039 <--LinkedList add
----------------------------------------------------------------------------------------------------
161940 <--ArrayList get 161939 <--ArrayList get 163406 <--ArrayList get
6065776 <--LinkedList get 5955129 <--LinkedList get 5856573 <--LinkedList get
----------------------------------------------------------------------------------------------------
248772 <--ArrayList remove 257564 <--ArrayList remove 253901 <--ArrayList remove
382500 <--LinkedList remove 380668 <--LinkedList remove 388728 <--LinkedList remove
嗯……基本没问题,LinkedList在add方面略有优势,但是在get和remove方面性能差较多,特别是get方法耗时是ArrayList的40倍左右
修改方法次数为10,000,再试
Test times = 10,000
----------------------------------------------------------------------------------------------------
5855107 <--ArrayList add 5932047 <--ArrayList add 6190711 <--ArrayList add
4621874 <--LinkedList add 4612349 <--LinkedList add 4777952 <--LinkedList add
----------------------------------------------------------------------------------------------------
1465884 <--ArrayList get 1553448 <--ArrayList get 1757155 <--ArrayList get
65177042 <--LinkedList get 65069692 <--LinkedList get 64154844 <--LinkedList get
----------------------------------------------------------------------------------------------------
2087263 <--ArrayList remove 2206703 <--ArrayList remove 2081034 <--ArrayList remove
3250883 <--LinkedList remove 3180538 <--LinkedList remove 3334052 <--LinkedList remove
可以看出add方面二者的差距逐渐在缩小,而get和remove则是差距越来越明显,隐隐有种预感
修改方法次数为100,000,再试
Test times = 100,000
----------------------------------------------------------------------------------------------------
13268426 <--ArrayList add 12983749 <--ArrayList add 13512068 <--ArrayList add
14465387 <--LinkedList add 15104353 <--LinkedList add 15269589 <--LinkedList add
----------------------------------------------------------------------------------------------------
5324590 <--ArrayList get 5245452 <--ArrayList get 5358297 <--ArrayList get
14461859917 <--LinkedList get 14871016175 <--LinkedList get 15722798537 <--LinkedList get
----------------------------------------------------------------------------------------------------
6943620 <--ArrayList remove 7003340 <--ArrayList remove 6839935 <--ArrayList remove
12048383 <--LinkedList remove 11443857 <--LinkedList remove 11187758 <--LinkedList remove
此时ArrayList在add方法的性能上已经超越了LinkedList,也就是意味着无论是add、get还是remove方法,ArrayList的性能都比LinkedList性能要好
修改方法次数为300,000,再试(本来想修改为100万的,但是LinkedList耗时太长所以放弃)
Test times = 300000
----------------------------------------------------------------------------------------------------
21563985 <--ArrayList add 19376701 <--ArrayList add 20826463 <--ArrayList add
61810016 <--LinkedList add 52228099 <--LinkedList add 57616806 <--LinkedList add
----------------------------------------------------------------------------------------------------
5531595 <--ArrayList get 5593513 <--ArrayList get 5213211 <--ArrayList get
187586464777 <--LinkedList get 184798077022 <--LinkedList get 193717198796 <--LinkedList get
----------------------------------------------------------------------------------------------------
7322823 <--ArrayList remove 7544848 <--ArrayList remove 7362391 <--ArrayList remove
15116077 <--LinkedList remove 14866938 <--LinkedList remove 14829935 <--LinkedList remove
可以看到,ArrayList在性能上已经遥遥领先,LinkedList难望其项背,相信即使数据再扩大,也必然是这个结果
为什么是这样的结果呢?那么,让我们来看看源码一探究竟吧
二、分析
首先找到ArrayList的add方法
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
第二行为将元素E赋值给size++的位置(size计数器+1)
第一行则是为私有方法,代码如下:
private void ensureCapacityInternal(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
modCount为java.util.AbstractList的成员变量,声明:
protected transient int modCount = 0;
这个变量作为ArrayList快速失败的机制的实现(面对并发的修改时)
然后进行判断,检查目前容量的长度+1是否大于数组的长度,如果是则对数组进行扩容
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
grow方法对数组进行扩容
int newCapacity = oldCapacity + (oldCapacity >> 1);
数组扩容的这一行,标记了扩充容量为原有容量的大约1.5倍
然后比较数组扩容后的长度以及将要到达的容量的长度,以及Integer的最大值等等,最后调用的是Arrays.copyOf方法,而该方法的底层实现为native的System.arraycopy方法,至此,了解了数组在add时处理的方法
分析其过程,即:
add方法被调用-->判断数组原有长度是否还够-->不够则扩充数组容量为1.5倍-->调用native方法复制数组
tips:ArrayList默认数组长度为10哦
我们再来看看LinkedList
/**
* Appends the specified element to the end of this list.
*
* <p>This method is equivalent to {@link #addLast}.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
linkLast(e);
return true;
}
其offer(E e)方法也是调用的add(E e)方法哦,但offerFirst和offerLast方法调用的分别是linkFirst和linkLast方法
来看看linkLast方法
/**
* Links e as last element.
*/
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++;
}
该方法第一行就有一个Node类,让我们来瞧瞧其真面目:
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
Node为LinkedList的静态内部类,其定义了三个属性,E为元素本身,next为后一元素,prev为前一元素
然后回过头来看linkLast方法
第一行将最后一个元素last定义为l
第二行创建新的内部类对象,该对象的前一个为l,本身为参数e,后一个为null(对象创建)
第三行将创建的对象赋值给last
接下来判断l是否为null(即原来的链表是否为空链表)然后进行赋值操作,计数器+1
linkFirst方法与linkLast方法类似,在此不做赘述
分析其过程,即:
add方法被调用-->创建引用赋值-->创建内部类对象-->改变引用赋值
三、结果
二者对比其add方法,不难发现
当数组较小时,因为ArrayList在添加元素时,需要频繁的去扩容且复制数组,因此时间开销很大,而LinkedList只是创建了一个对象,时间开销基本固定,因此二者的速度为LinkedList速度较快
但是当数组越来越大,ArrayList不需要那么频繁的扩容时(例如十万级别的容量,扩容一次容量后容量则达到了十五万,此后添加的五万条数据都是瞬间加入基本无耗时的开销),时间开销相对减小,但LinkedList则还是需要一个个的创建对象(十万到十五万,需要创建五万个对象),需要频繁的去申请内存然后在堆中创建对象,这样的时间消耗是巨大的(还不排除当内存不够时,还得启动GC回收内存后才能创建)
所以,ArrayList的效率会在达到一定的数量级别后要比LinkedList的add方法效率要高,至于这个级别是多少,是根据具体的环境、条件等等很多因素都会影响,毕竟ArrayList调用的是native方法,依赖于编译器的实现。
在继续测试了10,100两种次数后,发现remove方法也是在100次这个级别,ArrayList才超越了LinkedList,此前是linkedList占据优势,至于get方法,这个则没啥好比的,毕竟循环遍历链表怎么可能会比数组的下标更快对吧哈哈哈哈
四、反思
从上面的测试结果看来,即使ArrayList在做万级别数据的操作时,表现都是十毫秒级别的,这个时间基本可以忽略(一些特殊的应用除外),那么我们是否在大部分情况下,都可以使用ArrayList来存储对象操作对象呢?
这个就由各位看官自行把握吧,毕竟,LinkedList所具有的offerFirst、push等等方法,ArrayList都是不具有的哦,怎样选择,应该看具体需求吧!