JAVA遍历机制的性能的比较
    本文首发于cartoon的博客
    转载请注明出处:https://cartoonyu.github.io/cartoon-blog/post/java/java%E9%81%8D%E5%8E%86%E6%9C%BA%E5%88%B6%E7%9A%84%E6%80%A7%E8%83%BD%E6%AF%94%E8%BE%83/
缘由
    近段时间在写leetcode的Lemonade Change时候,发现了for循环与forEach循环的耗时是不一致的,在提交记录上面差了一倍......
    平常开发绝大部分业务逻辑的实现都需要遍历机制的帮忙,虽说也有注意到各数据结构操作的性能比较,但是忽视了遍历机制性能的差异。原本前两天就开始动手写,拖延症......
正文
现阶段我所知道JAVA遍历机制有三种
- for循环 
- forEach循环 
- Iterator循环 
JAVA数据结构千千万,但是大部分都是对基础数据结构的封装,比较HashMap依赖于Node数组,LinkedList底层是链表,ArrayList对数组的再封装......扯远了
总结来说,JAVA的基础数据结构,我觉得有两种
- 数组
- 链表
如果是加上Hash(Hash的操作与数组以及链表不太一致),就是三种
因为平常开发大部分都优先选择包装后的数据结构,所以下面我会使用
- ArrayList(包装后的数组)
- LinkedList(包装后的链表)
- HashSet(包装后的Hash类型数组)
这三种数据结构在遍历机制不同的时候时间的差异
可能有人对我为什么不对比HashMap呢,因为JAVA设计中,是先实现了Map,再实现Set。如果你有阅读过源码就会发现:每个Set子类的实现中,都有一个序列化后的Map对应属性实现,而因为Hash的查找时间复杂度为O(1),得出key后查找value的时间大致是一致的,所以我不对比HashMap。
题外话
我在阅读《疯狂JAVA》读到:JAVA的设计者将Map的内部entry数组中的value设为null进而实现了Set。因为我是以源码以及官方文档为准,具体我不清楚正确与否,但是因为Hash中的key互不相同,Set中元素也互不相同,所以我认为这个观点是正确的。
为了测试的公平性,我会采取以下的限定
- 每种数据结构的大小都设置三种量级
- 10
- 100
- 1000
 
- 元素都采用随机数生成
- 遍历进行操作都为输出当前元素的值
注:时间开销受本地环境的影响,可能测量值会出现变化,但是总体上比例是正确的
ArrayList的比较
- 代码 - public class TextArray { private static Random random; private static List<Integer> list1; private static List<Integer> list2; private static List<Integer> list3; public static void execute(){
 random=new Random();
 initArray();
 testForWith10Object();
 testForEachWith10Object();
 testIteratorWith10Object();
 testForWith100Object();
 testForEachWith100Object();
 testIteratorWith100Object();
 testForWith1000Object();
 testForEachWith1000Object();
 testIteratorWith1000Object();
 } private static void testForWith10Object(){
 printFor(list1);
 } private static void testForWith100Object(){
 printFor(list2);
 } private static void testForWith1000Object(){
 printFor(list3);
 } private static void testForEachWith10Object(){
 printForeach(list1);
 } private static void testForEachWith100Object(){
 printForeach(list2);
 } private static void testForEachWith1000Object(){
 printForeach(list3);
 } private static void testIteratorWith10Object() {
 printIterator(list1);
 } private static void testIteratorWith100Object() {
 printIterator(list2);
 } private static void testIteratorWith1000Object() {
 printIterator(list3);
 } private static void printFor(List<Integer> list){
 System.out.println();
 System.out.print("data:");
 long start=System.currentTimeMillis();
 for(int i=0,length=list.size();i<length;i++){
 System.out.print(list.get(i)+" ");
 }
 System.out.println();
 long end=System.currentTimeMillis();
 System.out.println("for for "+list.size()+":"+(end-start)+"ms");
 } private static void printForeach(List<Integer> list){
 System.out.println();
 System.out.print("data:");
 long start=System.currentTimeMillis();
 for(int temp:list){
 System.out.print(temp+" ");
 }
 System.out.println();
 long end=System.currentTimeMillis();
 System.out.println("foreach for "+list.size()+":"+(end-start)+"ms");
 } private static void printIterator(List<Integer> list){
 System.out.println();
 System.out.print("data:");
 Iterator<Integer> it=list.iterator();
 long start=System.currentTimeMillis();
 while(it.hasNext()){
 System.out.print(it.next()+" ");
 }
 System.out.println();
 long end=System.currentTimeMillis();
 System.out.println("iterator for "+list.size()+":"+(end-start)+"ms");
 } private static void initArray(){
 list1=new ArrayList<>();
 list2=new ArrayList<>();
 list3=new ArrayList<>();
 for(int i=0;i<10;i++){
 list1.add(random.nextInt());
 }
 for(int i=0;i<100;i++){
 list2.add(random.nextInt());
 }
 for(int i=0;i<1000;i++){
 list3.add(random.nextInt());
 }
 }
 }
 
- 输出(忽略对元素的输出) - for for 10:1ms
 foreach for 10:0ms
 iterator for 10:2ms for for 100:5ms
 foreach for 100:4ms
 iterator for 100:12ms for for 1000:33ms
 foreach for 1000:7ms
 iterator for 1000:16ms
 - 10 - 100 - 1000 - for - 1ms - 5ms - forEach - 0ms - 4ms - Iterator - 2ms - 12ms 
- 结论 - for的性能最不稳定,foreach次之,Iterator最好 
- 使用建议 - 在数据量不明确的情况下(可能1w,10w或其他),建议使用Iterator进行遍历 
- 在数据量明确且量级小的时候,优先使用foreach 
- 需要使用索引时,使用递增变量的开销比for的要小 
 
LinkedList的比较
- 代码 - public class TextLinkedList { private static Random random; private static List<Integer> list1; private static List<Integer> list2; private static List<Integer> list3; public static void execute(){
 random=new Random();
 initList();
 testForWith10Object();
 testForEachWith10Object();
 testIteratorWith10Object();
 testForWith100Object();
 testForEachWith100Object();
 testIteratorWith100Object();
 testForWith1000Object();
 testForEachWith1000Object();
 testIteratorWith1000Object();
 } private static void testForWith10Object() {
 printFor(list1);
 } private static void testForEachWith10Object() {
 printForeach(list1);
 } private static void testIteratorWith10Object() {
 printIterator(list1);
 } private static void testForWith100Object() {
 printFor(list2);
 } private static void testForEachWith100Object() {
 printForeach(list2);
 } private static void testIteratorWith100Object() {
 printIterator(list2);
 } private static void testForWith1000Object() {
 printFor(list3);
 } private static void testForEachWith1000Object() {
 printForeach(list3);
 } private static void testIteratorWith1000Object() {
 printIterator(list3);
 } private static void printFor(List<Integer> list){
 System.out.println();
 System.out.print("data:");
 long start=System.currentTimeMillis();
 for(int i=0,size=list.size();i<size;i++){
 System.out.print(list.get(i));
 }
 System.out.println();
 long end=System.currentTimeMillis();
 System.out.println("for for "+list.size()+":"+(end-start)+"ms");
 } private static void printForeach(List<Integer> list){
 System.out.println();
 System.out.print("data:");
 long start=System.currentTimeMillis();
 for(int temp:list){
 System.out.print(temp+" ");
 }
 System.out.println();
 long end=System.currentTimeMillis();
 System.out.println("foreach for "+list.size()+":"+(end-start)+"ms");
 } private static void printIterator(List<Integer> list){
 System.out.println();
 System.out.print("data:");
 Iterator<Integer> it=list.iterator();
 long start=System.currentTimeMillis();
 while(it.hasNext()){
 System.out.print(it.next()+" ");
 }
 System.out.println();
 long end=System.currentTimeMillis();
 System.out.println("iterator for "+list.size()+":"+(end-start)+"ms");
 } private static void initList() {
 list1=new LinkedList<>();
 list2=new LinkedList<>();
 list3=new LinkedList<>();
 for(int i=0;i<10;i++){
 list1.add(random.nextInt());
 }
 for(int i=0;i<100;i++){
 list2.add(random.nextInt());
 }
 for(int i=0;i<1000;i++){
 list3.add(random.nextInt());
 }
 }
 }
 
- 输出(忽略对元素的输出) - for for 10:0ms
 foreach for 10:1ms
 iterator for 10:0ms for for 100:1ms
 foreach for 100:0ms
 iterator for 100:3ms for for 1000:23ms
 foreach for 1000:25ms
 iterator for 1000:4ms
 - 10 - 100 - 1000 - for - 0ms - 1ms - forEach - 1ms - 0ms - Iterator - 0ms - 3ms 
- 结论 - foreach的性能最不稳定,for次之,Iterator最好 
- 使用建议 - 尽量使用Iterator进行遍历 
- 需要使用索引时,使用递增变量的开销比for的要小 
 
HashSet的比较
注:因Hash遍历算法与其他类型不一致,所以取消了for循环的比较
- 代码 - public class TextHash { private static Random random; private static Set<Integer> set1; private static Set<Integer> set2; private static Set<Integer> set3; public static void execute(){
 random=new Random();
 initHash();
 testIteratorWith10Object();
 testForEachWith10Object();
 testIteratorWith100Object();
 testForEachWith100Object();
 testIteratorWith1000Object();
 testForEachWith1000Object();
 } private static void testIteratorWith10Object() {
 printIterator(set1);
 } private static void testForEachWith10Object() {
 printForeach(set1);
 } private static void testIteratorWith100Object() {
 printIterator(set2);
 } private static void testForEachWith100Object() {
 printForeach(set2);
 } private static void testIteratorWith1000Object() {
 printIterator(set3);
 } private static void testForEachWith1000Object() {
 printForeach(set3);
 } private static void initHash() {
 set1=new HashSet<>();
 set2=new HashSet<>();
 set3=new HashSet<>();
 for(int i=0;i<10;i++){
 set1.add(random.nextInt());
 }
 for(int i=0;i<100;i++){
 set2.add(random.nextInt());
 }
 for(int i=0;i<1000;i++){
 set3.add(random.nextInt());
 }
 } private static void printIterator(Set<Integer> data){
 System.out.println();
 System.out.print("data:");
 long start=System.currentTimeMillis();
 Iterator<Integer> it=data.iterator();
 while (it.hasNext()){
 System.out.print(it.next()+" ");
 }
 System.out.println();
 long end=System.currentTimeMillis();
 System.out.println("iterator for "+data.size()+":"+(end-start)+"ms");
 } private static void printForeach(Set<Integer> data){
 System.out.println();
 System.out.print("data:");
 long start=System.currentTimeMillis();
 for(int temp:data){
 System.out.print(temp+" ");
 }
 System.out.println();
 long end=System.currentTimeMillis();
 System.out.println("foreach for "+data.size()+":"+(end-start)+"ms");
 }
 }
 
- 输出(忽略对元素的输出) - iterator for 10:0ms
 foreach for 10:0ms iterator for 100:6ms
 foreach for 100:0ms iterator for 1000:30ms
 foreach for 1000:9ms
 - 10 - 100 - 1000 - foreach - 0ms - 0ms - Iterator - 0ms - 6ms 
- 结论 - foreach性能遥遥领先于Iterator 
- 使用建议 - 以后就选foreach了,性能好,写起来也方便。 
总结
- for循环性能在三者的对比中总体落于下风,而且开销递增幅度较大。以后即使在需要使用索引时我宁愿使用递增变量也不会使用for了。
- Iterator的性能在数组以及链表的表现都是最好的,应该是JAVA的设计者优化过了。在响应时间敏感的情况下(例如web响应),优先考虑。
- foreach的性能属于两者之间,写法简单,时间不敏感的情况下我会尽量选用。
以上就是我对常见数据结构遍历机制的一点比较,虽然只是很初步,但是从中我也学到了很多东西,希望你们也有所收获。
如果你喜欢本文章,可以收藏阅读,如果你对我的其他文章感兴趣,欢迎到我的博客查看。
JAVA遍历机制的性能的比较的更多相关文章
- Java 集合 ArrayList和LinkedList的几种循环遍历方式及性能对比分析 [ 转载 ]
		Java 集合 ArrayList和LinkedList的几种循环遍历方式及性能对比分析 @author Trinea 原文链接:http://www.trinea.cn/android/arrayl ... 
- 剑指Offer——知识点储备-故障检测、性能调优与Java类加载机制
		剑指Offer--知识点储备-故障检测.性能调优与Java类加载机制 故障检测.性能调优 用什么工具可以查出内存泄露 (1)MerroyAnalyzer:一个功能丰富的java堆转储文件分析工具,可以 ... 
- 故障检测、性能调优与Java类加载机制
		故障检测.性能调优与Java类加载机制 故障检测.性能调优 用什么工具可以查出内存泄露 (1)MerroyAnalyzer:一个功能丰富的java堆转储文件分析工具,可以帮助你发现内存漏洞和减少内存消 ... 
- Arraylist的遍历方式、java反射机制
		先定义ArrayList再添加几条数据: ArrayList arr=new ArrayList(); //往arrList中增加几条数据 arr.add(1); arr.add(2) ... 
- [转]java反射机制
		原文地址:http://www.cnblogs.com/jqyp/archive/2012/03/29/2423112.html 一.什么是反射机制 简单的来说,反射机制指的是程序在运 ... 
- java 编程时候的性能调优
		一.避免在循环条件中使用复杂表达式 在不做编译优化的情况下,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快. 例子: import java.util ... 
- JAVA 画图机制
		java学习脚印:深入java绘图机制 写在前面 封装性越好的类在使用时,只要清楚接口即可,而不应该让程序员了解其内部结构; 对于平常的绘图来讲,java绘图机制无需了解太多,但是朦胧容易产生错误,绘 ... 
- java反射机制(转)
		一.什么是反射机制 简单的来说,反射机制指的是程序在运行时能够获取自身的信息.在java中,只要给定类的名字, 那么就可以通过反射机制来获得类的所有信息. 二.哪里用到反射机制 ... 
- Java高级开发_性能优化的细节
		一.核心部分总结: 尽量在合适的场合使用单例[减负提高效率] 尽量避免随意使用静态变量[GC] 尽量重用对象,避免过多过常地创建Java对象[最大限度地重用对象] 尽量使用final修饰符[内联(in ... 
随机推荐
- 面试官:你了解过Redis对象底层实现吗
			上一章我们讲了Redis的底层数据结构,不了解的人可能会有疑问:这个和平时用的五大对象有啥关系呢?这一章我们就主要解释他们所建立的联系. 看这个文件之前,如果对ziplist.skiplist.int ... 
- 「中高级前端必须了解的」JS中的内存管理
			前言 像C语言这样的底层语言一般都有底层的内存管理接口,比如 malloc()和free()用于分配内存和释放内存. 而对于JavaScript来说,会在创建变量(对象,字符串等)时分配内存,并且在不 ... 
- idea初见问题整理_错误: -source 1.5 中不支持 diamond 运算符
			最近在移动工程到idea下,顺便改目录结构,遇到的问题不一定全部记录,有些答案摘抄自别人博客,已注明来源,由于不是摘抄自同一作者,且有自己的一些内容,所以标注为原创. 1.(错误: -source 1 ... 
- 解读TIME_WAIT--你在网上看到的大多数帖子可能都是错误的
			由于TCP协议整个机制也非常复杂我只能尽可能的在某一条线上来说,不可能面面俱到,如果有疏漏或者对于内容有异议可以留言.谢谢大家. 查看服务器上各个状态的统计数量: netstat -ant | awk ... 
- (数据科学学习手札64)在jupyter notebook中利用kepler.gl进行空间数据可视化
			一.简介 kepler.gl是由Uber开发的进行空间数据可视化的开源工具,是Uber内部进行空间数据可视化的默认工具,通过其面向Python开放的接口包keplergl,我们可以在jupyter n ... 
- HDU 2896:病毒侵袭(AC自动机)
			http://acm.hdu.edu.cn/showproblem.php?pid=2896 题意:中文题意. 思路:AC自动机模板题.主要在于字符有128种,输出还要排序和去重! 注意是“total ... 
- os.path.join用法
			os.path.join()函数:连接两个或更多的路径名组件 1.如果各组件名首字母不包含’/’,则函数会自动加上 2.如果有一个组件是一个绝对路径,则在它之前的所有组件均会被舍弃 3.如果最后一个组 ... 
- scrapy基础知识之 scrapy 三种模拟登录策略:
			注意:模拟登陆时,必须保证settings.py里的 COOKIES_ENABLED (Cookies中间件) 处于开启状态 COOKIES_ENABLED = True或 # COOKIES_ENA ... 
- .Net Core 创建和使用中间件
			1. 定义中间内容 1.1 必须有一个RequestDelegate 委托用了进入一个中间件 1.2 通过构造函数设置这个RequestDelegate委托 1.3 必须有一个方法Task Invok ... 
- shell脚本常见错误一二三
			1.$'\r': 未找到命令的解决 2.: 不是有效的标识符h: 3.cd "$path"/webapps/ROOT 不能正常进入ROOT文件夹,$path并未与后面的字符结合起来 ... 
