ArrayList 应该是 Java 中最常用的集合类型了,以至于我们说到集合就会自然而然的想到 ArrayList。很多同学都没有用过除了 ArrayList 之外的其他集合,甚至于都已经忘了除了 ArrayList 之外的其他集合,例如 LinkedList、Vector 等。
那么我们平时只用 ArrayList 是不是正确呢,我既然知道了 LinkedList、Vector ,是不是也要用一下它呢,那么什么情况下用呢。
Vector 是线程安全的集合类型,所以仅在多线程编程的时候才考虑是否用它,凡是为了多线程设计,必然在性能上有所损失, Vector 只是在每个方法上简单的加上 synchronized
关键字,所以性能损失是肯定的。
那么现在就来比较一下 ArrayList 和 LinkedList,都说插入、删除操作多的话用 LinkedList,插入操作多的话用 ArrayList,产生这种说法的大致依据如下。
ArrayList
内部是用 Object 数组作为存储结构,数组是内存中连续的内存空间,并且继承了 RandomAccess
接口,所以可以实现元素的快速访问。但如果不是在末尾插入元素的话,需要拷贝插入位置之后的元素。
LinkedList
内部自己实现的 Node 链表,每个节点都指明了上一节点和下一节点, 所以不要求内存连续,并且插入数据只需要修改上一节点和下一节点的指针即可。但是访问元素需要遍历查找,所以查询元素较慢。
真的是这样吗?下面我做了一系列的测试,来看一下真实的情况。
插入数据
分别从头部、尾部和随机位置插入数据,初始的列表长度分别为 10、1w、10w 来测试几种插入数据的性能,以平均耗时为依据。得到的结果如下:
头部插入数据,ArrayList 耗时明显大于 LinkedList ,得出结论头部插入数据 LinkedList 性能优于 ArrayList,因为在头部位置插入数据时,ArrayList 要拷贝插入位置之后的数据,往后移动一个位置。
尾部插入数据,ArrayList 耗时小于 LinkedList,得出结论:尾部插入数据,ArrayList 性能优于 LinkedList。
随机位置是用 Random 产生的,上限是当前列表长度。可见随机位置插入数据,ArrayList 耗时小于 LinkedList ,得出结论:多次随机位置插入数据,平均性能 ArrayList 优于 LinkedList,注意,这里说的是多次随机插入求的平均值。
插入数据得出以下结论
插入位置 | 初始列表长度10 | 初始列表长度1w | 初始列表长度10w |
---|---|---|---|
头部 | LinkedList 优于 ArrayList | LinkedList 优于 ArrayList | LinkedList 优于 ArrayList |
尾部 | ArrayList 优于 LinkedList | ArrayList 优于 LinkedList | ArrayList 优于 LinkedList |
多次随机位置 | ArrayList 优于 LinkedList | ArrayList 优于 LinkedList | ArrayList 优于 LinkedList |
获取数据
分别从头部、尾部和随机位置获取数据,初始的列表长度分别为1000、10w、100w、1000w 来测试几种获取数据的性能,以吞吐量为判断依据,吞吐量单位是 每毫秒内操作数。得到的结果如下:
头部获取数据,ArrayList 吞吐量大于 LinkedList,得出结论头部获取数据,ArrayList 性能优于 LinkedList
尾部获取数据,ArrayList 吞吐量大于 LinkedList,得出结论尾部获取数据,ArrayList 性能优于 LinkedList
多次随机位置获取数据,ArrayList 吞吐量大于 LinkedList,得出结论多次随机位置获取数据,ArrayList 性能优于 LinkedList
综上,获取数据操作的结论如下
获取位置 | 列表长度1000 | 列表长度10w | 列表长度100w | 列表长度1000w |
---|---|---|---|---|
头部 | ArrayList 优于 LinkedList | ArrayList 优于 LinkedList | ArrayList 优于 LinkedList | ArrayList 优于 LinkedList |
尾部 | ArrayList 优于 LinkedList | ArrayList 优于 LinkedList | ArrayList 优于 LinkedList | ArrayList 优于 LinkedList |
多次随机位置 | ArrayList 优于 LinkedList | ArrayList 优于 LinkedList | ArrayList 优于 LinkedList | ArrayList 优于 LinkedList |
这是毋庸置疑的,实现了随机访问并且内部以数组形式存储数据的 ArrayList 拥有明显的优势。
删除数据
分别从头部、尾部和随机位置获取数据,初始的列表长度分别为1w、100w、1000w 来测试几种删除数据的性能,以吞吐量为判断依据,吞吐量单位是 每毫秒内操作数。得到的结果如下:
头部删除数据,LinkedList 吞吐量大于 ArrayList,得出结论:头部删除数据,LinkedList 性能优于 ArrayList
尾部删除数据,ArrayList 吞吐量大于 LinkedList,得出结论:尾部删除数据,ArrayList 性能优于 LinkedList
多次随机位置删除数据,ArrayList 吞吐量大于 LinkedList,得出结论:尾部删除数据,ArrayList 性能优于 LinkedList
综上,删除数据操作的结论如下
获取位置 | 列表长度100w | 列表长度1000w |
---|---|---|
头部 | LinkedList 优于 ArrayList | LinkedList 优于 ArrayList |
尾部 | ArrayList 优于 LinkedList | ArrayList 优于 LinkedList |
多次随机位置 | ArrayList 优于 LinkedList | ArrayList 优于 LinkedList |
遍历数据
分别比较了 5 种遍历方式在1000、100w、1000w 长度的列表下的性能状况。
遍历方式一,原始形式的 for 循环
for(int i = 0;i<x;i++){
//do something
}
遍历方式二,增强形式的 for 循环
for(Integer i : arrayList){
// do something
}
遍历方式三,迭代器方式
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()){
// do something
}
遍历方式四,forEach 方式
arrayList.forEach(integer -> {
// do something
});
遍历方式五,stream 方式
正常来讲,stream 后面再接 forEach 是不太正确的测试方式,一般 stream 都会配合 map、filter 等操作来进行筛选、计算等。这里为了方面测试,姑且先这个写了。
arrayList.stream().forEach(integer -> {
// do something
});
ArrayList 这 5 种方式遍历的性能
LinkedList 这 5 种方式遍历的性能
比较这五种方式下 ArrayList 和 LinkedList 的性能
LinkedList 只有在头部插入和删除数据频繁的时候才有优势,但是能找到这种应用场景似乎也不容易找到。如此看来,我们在程序中用到列表就随手 new 一个 ArrayList 倒也很有道理。
不要吝惜你的「推荐」呦
本文中的测试使用 JMH 基准测试完成的,图表是用 Excel 做的,如果需要源码和 Excel 文件,请在公众号内回复关键词 「jmh」
欢迎关注,不定期更新本系列和其他文章
古时的风筝
,进入公众号可以加入交流群