最近coding的时候对循环的性能很好奇,面对多种循环方式,迭代器、for循环、forEach循环、lambda的forEach循环。
如果单论代码美观,个人偏向于lambda的forEach循环和forEach循环。但是这样唐突的选择很有可能造成性能的下降。
后面本人就分析了一下各个循环之间的效率:
一、ArrayList
size=100000
for:8
forEach:12
Iterator:11
lambda:115
size=1000
for:4
forEach:1
Iterator:0
lambda:98
size=10000
for:6
forEach:2
Iterator:1
lambda:107
可以看出,当size很大(10万)时候for循环的性能最高,当size不是很大的时候Iterator和forEach效率就高。ArrayList的底层是数组形式,这不得不从它们的取数逻辑入手,如下:
(1)foreach循环和iterator迭代器:
foreach的底层是通过iterator循环,通过调用iterator.next(),查看ArrayList对于iterator中next方法的实现可知其最终是通过数组下标获取元素。
(2)for循环
for语句快里直接通过下标取数
(3)lambda的forEach循环
开启多线程并发处理
从如上遍历、取数逻辑中可得出,for在数据量小时,由于多了index++、index<size这两步操作,所以在此时效率要小于foreach循环和iterator迭代器。当数据量大的时候,for对index++、index<size这两步操作的开销小于iterator转化成下标取数的开销了,此时for循环就会变得效率最高了。而lambda的forEach循环由于是开启多线程在取数,频繁线程切换,所以效率也就不尽人意。
对于如何选择,总结了如下几点,可参考
1.如果是需要对下标进行处理操作对则选择for循环;
2.对于ArrayList,从代码简洁讲优先考虑foreach,也不用去考虑下标越界对问题;
3.对于实效性要求不高的,如I/O操作,可考虑lambda的forEach循环。
二、LinkedList
size=100000
for:18
forEach:10
Iterator:9
lambda:108
size=10000
for:9
forEach:3
Iterator:2
lambda:104
size=1000
for:4
forEach:1
Iterator:0
lambda:114
此时Iterator性能最佳,由于LinkedList的数据结构为链表结构,其中每个节点都记录了前驱和后继结点(头、尾除外)。通过下标的方式获得值都会在链中检查一遍,直到得到我们想要的下标节点为止。而Iterator和forEach则是始终循环子节点,直到尾节点为止。考虑到代码简洁,建议使用forEach。
三、HashMap
size=100000
forEach:17
Iterator:13
lambda:125
size=10000
forEach:4
Iterator:3
lambda:102
size=1000
forEach:1
Iterator:1
lambda:11
这里我测试了三组数据(都是取entrySet为例),不难发现Iterator循环始终效率方面要占优,这里简单的提下HashMap的放值和取值原理。
HashMap是一个散列桶,由数组和链表组成。每次put操作都会得到key的hash值,然后根据得到的hash值选择map数组对应的下标存入bucket(存入键对象和值对象)。而get操作时是根据键对象的hash值找map数组中对应的bucket,然后得到对应的值(当存在hash碰撞时,会以链表的形式加在相同hash值的后面,每次get的时候调用keys.equals()得到该键与之对应的值)。
在之前的List对比中已经说明forEach循环底层也是由Iterator实现,而lambda的forEach循环是充分利用cup实现多线程的迭代。所以不难得出HashMap遍历Iterator效率更高一点,但是基于代码简洁性考虑也可采用forEach循环方式。