本文首发于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最好

  • 使用建议

    1. 在数据量不明确的情况下(可能1w,10w或其他),建议使用Iterator进行遍历

    2. 在数据量明确且量级小的时候,优先使用foreach

    3. 需要使用索引时,使用递增变量的开销比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最好

  • 使用建议

    1. 尽量使用Iterator进行遍历

    2. 需要使用索引时,使用递增变量的开销比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了,性能好,写起来也方便。

总结

  1. for循环性能在三者的对比中总体落于下风,而且开销递增幅度较大。以后即使在需要使用索引时我宁愿使用递增变量也不会使用for了。
  2. Iterator的性能在数组以及链表的表现都是最好的,应该是JAVA的设计者优化过了。在响应时间敏感的情况下(例如web响应),优先考虑。
  3. foreach的性能属于两者之间,写法简单,时间不敏感的情况下我会尽量选用。

    以上就是我对常见数据结构遍历机制的一点比较,虽然只是很初步,但是从中我也学到了很多东西,希望你们也有所收获。

    如果你喜欢本文章,可以收藏阅读,如果你对我的其他文章感兴趣,欢迎到我的博客查看。

JAVA遍历机制的性能的比较的更多相关文章

  1. Java 集合 ArrayList和LinkedList的几种循环遍历方式及性能对比分析 [ 转载 ]

    Java 集合 ArrayList和LinkedList的几种循环遍历方式及性能对比分析 @author Trinea 原文链接:http://www.trinea.cn/android/arrayl ...

  2. 剑指Offer——知识点储备-故障检测、性能调优与Java类加载机制

    剑指Offer--知识点储备-故障检测.性能调优与Java类加载机制 故障检测.性能调优 用什么工具可以查出内存泄露 (1)MerroyAnalyzer:一个功能丰富的java堆转储文件分析工具,可以 ...

  3. 故障检测、性能调优与Java类加载机制

    故障检测.性能调优与Java类加载机制 故障检测.性能调优 用什么工具可以查出内存泄露 (1)MerroyAnalyzer:一个功能丰富的java堆转储文件分析工具,可以帮助你发现内存漏洞和减少内存消 ...

  4. Arraylist的遍历方式、java反射机制

    先定义ArrayList再添加几条数据:         ArrayList arr=new ArrayList(); //往arrList中增加几条数据 arr.add(1); arr.add(2) ...

  5. [转]java反射机制

    原文地址:http://www.cnblogs.com/jqyp/archive/2012/03/29/2423112.html 一.什么是反射机制         简单的来说,反射机制指的是程序在运 ...

  6. java 编程时候的性能调优

    一.避免在循环条件中使用复杂表达式 在不做编译优化的情况下,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快. 例子: import java.util ...

  7. JAVA 画图机制

    java学习脚印:深入java绘图机制 写在前面 封装性越好的类在使用时,只要清楚接口即可,而不应该让程序员了解其内部结构; 对于平常的绘图来讲,java绘图机制无需了解太多,但是朦胧容易产生错误,绘 ...

  8. java反射机制(转)

    一.什么是反射机制         简单的来说,反射机制指的是程序在运行时能够获取自身的信息.在java中,只要给定类的名字,     那么就可以通过反射机制来获得类的所有信息. 二.哪里用到反射机制 ...

  9. Java高级开发_性能优化的细节

    一.核心部分总结: 尽量在合适的场合使用单例[减负提高效率] 尽量避免随意使用静态变量[GC] 尽量重用对象,避免过多过常地创建Java对象[最大限度地重用对象] 尽量使用final修饰符[内联(in ...

随机推荐

  1. 常用URL分享,实用地址

    常用地址 文库文档免费下载地址1:http://www.hiwenku.com/ 文库文档免费下载下载2:http://www.20009.net/wk.html google地图拾取器:http:/ ...

  2. Django学习笔记(20)——BBS+Blog项目开发(4)Django如何使用Bootstrap

    本文学习如何通过Django使用Bootstrap.其实在之前好几个Django项目中已经尝试使用过了Bootstrap,而且都留有学习记录,我已经大概有了一个大的框架,那么本文就从头再走一遍流程,其 ...

  3. 深入理解Java的浅克隆与深克隆

    前言 克隆,即复制一个对象,该对象的属性与被复制的对象一致,如果不使用Object类中的clone方法实现克隆,可以自己new出一个对象,并对相应的属性进行数据,这样也能实现克隆的目的. 但当对象属性 ...

  4. 【java自定义注解1】java自定义注解-属性

    关于自定义注解,以前项目种应用的不多,最近看新项目过程中发现了挺多自定义注解相关内容,使用起来比较巧妙,于是 总结了两种方式,记录如下: 第一种:结合反射进行属性注入,代码如下: 1.定义一个注解: ...

  5. tar命令中的 -C 作用

    我用这个命令:tar zcvf chao.tar.gz /chao/*  打包文件的时候,在压缩包里把  /chao/这个路径也打包进去了. [root@yunwei-test chao]# ls / ...

  6. Ruby中的常量:引号、%符号和heredoc

    数值字面量 没什么好说的,唯一需要说明的是分数字面量:数值后加上一个后缀字母r表示分数字面量. # 整数字面量 0 1 100 10_000_001 # 千分位 # 浮点数字面量 0.1 1.0 1. ...

  7. python爬虫的一个常见简单js反爬

    python爬虫的一个常见简单js反爬 我们在写爬虫是遇到最多的应该就是js反爬了,今天分享一个比较常见的js反爬,这个我已经在多个网站上见到过了. 我把js反爬分为参数由js加密生成和js生成coo ...

  8. eclipse中一个项目引用另一个项目,运行报:java.lang.NoClassDefFoundError

    项目右击-properties-Java Build Path -Porjects-add.选中了某个项目. 项目用tomcat启动时,报错:java.lang.NoClassDefFoundErro ...

  9. 西门子PLC中各个组织块OB作用(OB1、OB100……)

    1.自由循环组织块OB1 S7  CPU启动完成后,操作系统循环执行OB1,OB1执行完成后,操作系统再次启动OB1.在OB1中可以调用FB.SFB.FC.SFC等用户程序使其循环执行.除OB90以外 ...

  10. 干货分享:ASP.NET CORE(C#)与Spring Boot MVC(JAVA)异曲同工的编程方式总结

    目录 C# VS JAVA 基础语法类比篇: 一.匿名类 二.类型初始化 三.委托(方法引用) 四.Lambda表达式 五.泛型 六.自动释放 七.重写(override) ASP.NET CORE ...