您的位置:首页 > 编程语言 > Java开发

Java中关于ArrayList以及LinkedList

2017-01-11 09:36 274 查看

一、疑问产生

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都是不具有的哦,怎样选择,应该看具体需求吧!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  arraylist linkedlist
相关文章推荐