【Java】Java中使用HashMap优化多层for循环嵌套以及for循环之性能优化

前言

for循环是开发时常用的语法之一,比如对数组,集合的遍历等,但是如果使用不好也会出现很多新能损耗的问题,今天就来讲解一下for循环的常用性能优化问题。

for循环 里面还有 for循环, 然后做一些数据匹配、处理 这种场景。

使用HashMap优化多级嵌套循环

m层嵌套的n次的for循环的时间复杂度为O(n^m),如下所示:

优化前

public class forTradition {
    
    
 public static void main(String[] args){
    
    
 
     List<Student> stus = new ArrayList<>();
     //  为stus写入10条数据 ... 略
     List<Grade> gs = new ArrayList<>();
     //  为gs写入10条数据 ... 略
     
     for (int i = 0 ; i < stus.size() ; i++ ) {
    
    
         Student stu = stus.get(i);
         Integer id = stu.getId();
         String stuName = stu.getName();
         for (int j = 0 ; j < gs.size() ; j++ ) {
    
    
         	Grade g = gs.get(j);
         	if( id == g.getStuId() ) {
    
     
	            System.out.println( "学生:" + stuName + ",成绩:" + g.getValue() );
         	}
    	 }
 	 }
}

优化后

public class forNew {
    
    
 public static void main(String[] args){
    
    
 
     List<Student> stus = new ArrayList<>();
     //  为stus写入10条数据 ... 略
     List<Grade> gs = new ArrayList<>();
	 //  为gs写入10条数据 ... 略
	 
	 Map<Integer,Integer> gradesMap = 
	 		gs.stream().collect(Collectors.toMap( data -> data.getStuId() , data -> data.getValue() );
      
     for (int i = 0 ; i < stus.size() ; i++ ) {
    
    
         Student stu = stus.get(i);
         Integer value = gradesMap.get(stu.getId());
         if( null != value ) {
    
    
         	System.out.println( "学生:" + stu.getName() + ",成绩:" + value );
		 }
	 }
  }
}

for循环之性能优化

嵌套循环

嵌套循环是有俩层或者俩层以上的循环嵌套在一起,下面直接上代码说明。

外大内小嵌套

   /**
     * 大循环驱动小循环(即外大内小)
     */
    private static void bigSmall() {
    
    
        long stratTime = System.nanoTime();
        for (int i = 0; i < 10000000; i++) {
    
    
            for (int j = 0; j < 100; j++) {
    
    

            }
        }
        long endTime = System.nanoTime();
        System.out.println("外大内小耗时:" + (endTime - stratTime));
    }

执行看一下结果耗时:

外大内小耗时:8743800

再看看外小内大耗时情况

外小内大嵌套

   /**
     * 小循环驱动大循环(即外小内大)
     */
    private static void smallBig() {
    
    
        long stratTime = System.nanoTime();
        for (int i = 0; i < 100; i++) {
    
    
            for (int j = 0; j < 10000000; j++) {
    
    

            }
        }
        long endTime = System.nanoTime();
        System.out.println("外小内大耗时:" + (endTime - stratTime));
    }

执行看一下结果耗时:

外小内大耗时:6922600

好了,综合比较一下俩者的执行时间,时差还是很大的。

外小内大耗时:6922600 ;外大内小耗时:8743800

分析总结

由以上对比可知,优化后性能显著提升。嵌套循环应该遵循“外小内大”的原则,虽然循环次数没变,但是耗时却长了很大。这就好比你复制很多个小文件和复制几个大文件的区别,虽然总的大小没变,但是复制大文件明显比多个小文件更快。

循环变量的实例化

把循环变量的实例放在循环内:

/**
 * 循环变量放在循环内
 */
private static void smallBigBetterTwo() {
    
    
    long stratTime = System.nanoTime();
    for (int i = 0; i < 100; i++) {
    
    
        for (int j = 0; j < 10000000; j++) {
    
    

        }
    }
    long endTime = System.nanoTime();
    System.out.println("循环内变量耗时:" + (endTime - stratTime));
}

执行耗时:

循环内变量耗时:4934500

把循环变量的实例放在循环之外:

/**

  • 循环变量放在循环外
    */
    private static void smallBigBetter() {
    long stratTime = System.nanoTime();
    int i, j;
    for (i = 0; i < 100; i++) {
    for (j = 0; j < 10000000; j++) {

     }
    

    }
    long endTime = System.nanoTime();
    System.out.println(“循环外变量耗时:” + (endTime - stratTime));
    }

执行耗时:

循环外变量耗时:5013800

对比一下把变量放在循环内和循环外对比耗时,发现时差还是挺大的:

循环内变量耗时:4934500;循环外变量耗时:5013800

分析总结

虽然优化效果并不明显,但是随着循环次数的增加,耗时会越来越大,优化效果则会越来越明显。分析:优化前需要实例化1+i=101次,优化后仅仅2次。总结:循环变量的实例化应放在循环外。

提取与循环无关的表达式

没有提取无关的表达式

   /**
     * 未提取无关的表达式
     */
    private static void calculationInner() {
    
    
        int a = 3;
        int b = 7;
        long stratTime = System.nanoTime();
        for (int i = 0; i < 10000000; i++) {
    
    
            i = i * a * b;
        }
        long endTime = System.nanoTime();
        System.out.println("未提取耗时:" + (endTime - stratTime));

    }

没有提取耗时:

未提取耗时:800

提取了无关的表达式

/**
 * 提取无关的表达式
 */
private static void calculationOuter() {
    
    
    int a = 3;
    int b = 7;
    int c = a * b;
    long stratTime = System.nanoTime();
    for (int i = 0; i < 10000000; i++) {
    
    
        i = i * c;
    }
    long endTime = System.nanoTime();
    System.out.println("已提取耗时:" + (endTime - stratTime));
}

提取了无关表达式耗时:

已提取耗时:500

分析总结

代码中a*b与循环无关,所以应该把它放到外面,避免重复计算。从理论角度分析,由于减少了计算次数,故优化后性能会更高。

消除循环终止判断时的方法调用

stratTime = System.nanoTime();
for (int i = 0; i < list.size(); i++) {
    
     

}
endTime = System.nanoTime();
System.out.println("未优化list耗时:"+(endTime - stratTime));

耗时:

未优化list耗时:253800

优化后

stratTime = System.nanoTime();
int size = list.size();
for (int i = 0; i < size; i++) {
    
     

}
endTime = System.nanoTime();
System.out.println("优化list耗时:"+(endTime - stratTime));

耗时:

优化list耗时:142500

分析总结

每次循环,list.size()都会被执行一次,这无疑会影响程序的性能,所以应该将其放到循环外面,用一个变量来缓存其size,不要让这一点点代码而消耗我们这么多性能。

异常捕获

在内部捕获异常

/**
     * 在内部捕获异常
     */
    private static void catchInner() {
    
    
        long stratTime = System.nanoTime();
        for (int i = 0; i < 10000000; i++) {
    
    
            try {
    
    
            } catch (Exception e) {
    
    
            }
        }
        long endTime = System.nanoTime();
        System.out.println("在内部捕获异常耗时:" + (endTime - stratTime));
    }

执行耗时:

在内部捕获异常耗时:3352700

在外部捕获异常

/**
 * 在外部捕获异常
 */
private static void catchOuter() {
    
    
    long stratTime = System.nanoTime();
    try {
    
    
        for (int i = 0; i < 10000000; i++) {
    
    
        }
    } catch (Exception e) {
    
    

    }
    long endTime = System.nanoTime();
    System.out.println("在外部捕获异常耗时:" + (endTime - stratTime));
}

执行耗时:

在外部捕获异常耗时:2893600

分析总结:

捕获异常很占用资源,所以不要把try catch放到循环内部,优化后性能同样有好几个数量级的提升。另外, 《Effective Java》一书指出for-each循环优先于传统的for循环,它在简洁性和预防bug方面有着传统for循环无法媲美的优势,并且,没有性能方面的损失,因此,推荐使用for-each循环。

猜你喜欢

转载自blog.csdn.net/u011397981/article/details/131783676