基准测试了 ArrayList 和 LinkedList ,发现我们一直用 ArrayList 也是没什么问题的
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 种方式遍历的性能
无论是1w、100w、1000w ,这五种方式的性能排名都是基本稳定的。原始 for 循环最优,foreach 方式最差。
linkedlist 这 5 种方式遍历的性能
无论是1w、100w、1000w ,这五种方式的性能排名都是基本稳定的。和 arraylist 相似,都是原始 for 循环最优,foreach 方式最差,其他三种方式相差不大。
比较这五种方式下 arraylist 和 linkedlist 的性能
观察发现,迭代器方式、stream 方式、增强 for 循环这三种方式,arraylist 的性能基本上都要优于 linkedlist,而最原始的 for 循环方式,直到列表长度达到了 1000w,仍然是 linkedlist 优于 arraylist。
linkedlist 只有在头部插入和删除数据频繁的时候才有优势,但是能找到这种应用场景似乎也不容易找到。如此看来,我们在程序中用到列表就随手 new 一个 arraylist 倒也很有道理。
不要吝惜你的「推荐」呦
本文中的测试使用 jmh 基准测试完成的,图表是用 excel 做的,如果需要源码和 excel 文件,请在公众号内回复关键词 「jmh」
欢迎关注,不定期更新本系列和其他文章古时的风筝
,进入公众号可以加入交流群