java遍历机制性能的比较详解

缘由

近段时间在写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 33ms
forEach 0ms 4ms 7ms
Iterator 2ms 12ms 16ms

结论

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 23ms
forEach 1ms 0ms 25ms
Iterator 0ms 3ms 4ms

结论

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 9ms
Iterator 0ms 6ms 30ms

结论

foreach性能遥遥领先于Iterator

使用建议

以后就选foreach了,性能好,写起来也方便。

总结

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

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

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • java获取反射机制的3种方法总结

    反射机制的概念: 指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能调用它的任意一个方法.这种动态获取信息,以及动态调用对象方法的功能叫java语言的反射机制. 反射机制的应用: 生成动态代理,面向切片编程(在调用方法的前后各加栈帧). 反射机制的原理: 1 首先明确的概念: 一切皆对象----类也是对象. 2 然后知道类中的内容 :modifier constructor field method. 3 其次明白加载: 当Animal.class在硬盘中时

  • Java中的RASP机制实现详解

    RSAP RASP是Gartner公司提出的一个概念,称:程序不应该依赖于外部组件进行运行时保护,而应该自身拥有运行时环境保护机制: RASP就是运行时应用自我保护(Runtime application self-protection)的缩写,正如RASP字面意思一样,这是运行在运行时的一种防护技能:也就是说RASP能够在程序运行期间实施自我保护,监控与过滤有害信息,还能够拥结合程序的当前上下文实施精确.实时的防护: Java中的RASP 不严格来说Java是半编译.半解释型语言,我们也都知道

  • 利用java反射机制调用类的私有方法(推荐)

    试想一下,如果你可以轻易地调用一个类的私有方法,那么是不是说你的封装都失效了?最近在看java的反射机制,发现居然可以利用java的反射机制去调用其他类的私有方法,至于这能干什么,那就见人见智了.. 我写的一段简易实例代码如下: import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * @author thomaslwq * @version 创建时间:Sep 4, 201

  • java的异常与处理机制分析【附面试题】

    本文实例讲述了java的异常与处理机制.分享给大家供大家参考,具体如下: java的异常机制 Throwable类 Throwable类是Java异常类型的顶层父类,一个对象只有是 Throwable 类的(直接或者间接)实例,他才是一个异常对象,才能被异常处理机制识别.JDK中内建了一些常用的异常类,我们也可以自定义异常. Throwable又派生出Error类和Exception类. 错误:Error类以及他的子类的实例,代表了JVM本身的错误.错误不能被程序员通过代码处理,Error很少出

  • Java多线程之异步Future机制的原理和实现

    项目中经常有些任务需要异步(提交到线程池中)去执行,而主线程往往需要知道异步执行产生的结果,这时我们要怎么做呢?用runnable是无法实现的,我们需要用callable看下面的代码: import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurren

  • 详解JAVA类加载机制(推荐)

    JAVA源码编译由三个过程组成: 1.源码编译机制. 2.类加载机制 3.类执行机制 我们这里主要介绍编译和类加载这两种机制. 一.源码编译 代码编译由JAVA源码编译器来完成.主要是将源码编译成字节码文件(class文件).字节码文件格式主要分为两部分:常量池和方法字节码. 二.类加载 类的生命周期是从被加载到虚拟机内存中开始,到卸载出内存结束.过程共有七个阶段,其中到初始化之前的都是属于类加载的部分 加载----验证----准备----解析-----初始化----使用-----卸载 系统可能

  • 详解java 三种调用机制(同步、回调、异步)

    1:同步调用:一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用 2:回调:一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口: 3:异步调用:一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口 具体说来:就是A类中调用B类中的某个方法C,然后B类中反过来调用A类中的方法D,D这个方法就叫回调方法, 实例1:使用java中Timer来在给定时间间隔发送通知,每隔十秒打印一次数据 Tim

  • Java编程Retry重试机制实例详解

    本文研究的主要是Java编程Retry重试机制实例详解,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下 1.业务场景 应用中需要实现一个功能: 需要将数据上传到远程存储服务,同时在返回处理成功情况下做其他操作.这个功能不复杂,分为两个步骤:第一步调用远程的Rest服务逻辑包装给处理方法返回处理结果:第二步拿到第一步结果或者捕捉异常,如果出现错误或异常实现重试上传逻辑,否则继续逻辑操作. 2.常规解决方案演化 1)try-catch-redo简单重试模式: 包装正

  • java遍历机制性能的比较详解

    缘由 近段时间在写leetcode的Lemonade Change时候,发现了for循环与forEach循环的耗时是不一致的,在提交记录上面差了一倍...... 平常开发绝大部分业务逻辑的实现都需要遍历机制的帮忙,虽说也有注意到各数据结构操作的性能比较,但是忽视了遍历机制性能的差异.原本前两天就开始动手写,拖延症...... 正文 现阶段我所知道JAVA遍历机制有三种 for循环 forEach循环 Iterator循环 JAVA数据结构千千万,但是大部分都是对基础数据结构的封装,比较HashM

  • Java 反射机制原理与用法详解

    本文实例讲述了Java 反射机制原理与用法.分享给大家供大家参考,具体如下: 反射反射,程序员的快乐! 1.什么是反射? Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性:并且能改变它的属性.而这也是Java被视为动态(或准动态,为啥要说是准动态,因为一般而言的动态语言定义是程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言.从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是

  • Java反射机制及Method.invoke详解

    JAVA反射机制 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法:这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制. Java反射机制主要提供了以下功能: 在运行时判断任意一个对象所属的类:在运行时构造任意一个类的对象:在运行时判断任意一个类所具有的成员变量和方法:在运行时调用任意一个对象的方法:生成动态代理. 1. 得到某个对象的属性 复制代码 代码如下: public Object get

  • Java泛型机制的程序演示详解

    本文为大家分享了Java泛型机制的程序演示具体代码,供大家参考,具体内容如下 package packA; import java.util.*; public class GenericDemo { public static void main(String[] args) { TreeSet<String> ts = new TreeSet<String>( new LenSort() ); //<String> 泛型 ts.add("hidwju&qu

  • Java异常处理机制try catch流程详解

    在项目中遇到try...catch...语句,因为对Java异常处理机制的流程不是很清楚,导致对相关逻辑代码不理解.所以现在来总结Java异常处理机制的处理流程: 1.异常处理的机制如下:在方法中用 try... catch... 语句捕获并处理异常,catch 语句可以有多个,用来匹配多个不同类型的异常.对于处理不了的异常或者要转型的异常,在方法的声明处通过 throws 声明异常,通过throw语句拋出异常,即由上层的调用方法来处理该异常. try { 逻辑程序块 } catch(Excep

  • Java事件机制要素及实例详解

    java事件机制中包含下述三要素: 1.事件,发生了什么事,比如用户在界面上的一个操作(手势滑动屏幕),当一个事件发生的时候,该事件用一个事件对象表示,每一个事件对象都有其对应的事件类. Java中事件一般继承自java.util.EventObject类,封装了事件源对象,以及事件的相关信息. 每一类事件有一个相应的事件监听器接口,该接口定义了接收和处理事件的抽象方法.实现该接口的类,就是监听器类.其对象可作为监听器对象向相应的组件注册.事件的类名通常为:XxxEvent ,比如下面实例中的C

  • Java中map遍历方式的选择问题详解

    1. 阐述 对于Java中Map的遍历方式,很多文章都推荐使用entrySet,认为其比keySet的效率高很多.理由是:entrySet方法一次拿到所有key和value的集合:而keySet拿到的只是key的集合,针对每个key,都要去Map中额外查找一次value,从而降低了总体效率.那么实际情况如何呢? 为了解遍历性能的真实差距,包括在遍历key+value.遍历key.遍历value等不同场景下的差异,我试着进行了一些对比测试. 2. 对比测试 一开始只进行了简单的测试,但结果却表明k

  • java中注解机制及其原理的详解

    java中注解机制及其原理的详解 什么是注解 注解也叫元数据,例如我们常见的@Override和@Deprecated,注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包.类.接口.字段.方法参数.局部变量等进行注解.它主要的作用有以下四方面: 生成文档,通过代码里标识的元数据生成javadoc文档. 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证. 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码. 运行时动态处理,运行时通过代码里标识

  • Java Iterator接口遍历单列集合迭代器原理详解

    这篇文章主要介绍了Java Iterator接口遍历单列集合迭代器原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Iterator接口概述 在程序开发中,经常需要遍历集合中的所有元素.针对这种需求,JDK专门提供了一个接口java.util.Iterator . Iterator 接口也是Java集合中的一员,但它与 Collection . Map 接口有所不同,Collection 接口与 Map 接口主要用于存储元素,而 Iter

  • java性能分析jconsole详解

    目录 jconsole简介 jconsole远程 前言: 本章节继续学习java性能优化的相关知识.重点学习什么是jconsole,以及如何使用?它能帮助我们做什么? jconsole简介 提供JVM图形化视图,包括内存.线程.类.cpu等信息.用户可以通过jconsole工具去连接指定的jvm,监控jvm的变化. 我们可以在jdk的安装文件bin当中找到它: 双击运行会打开如下界面,上面是本地的java进程,下面是通过远程的方式连接服务器上面的java进程. 我们随便点击一个本地进程得到如下的

随机推荐