java中switch 和lf...else if语句的效率问题

一、java中对switch的处理

        switch是控制选择的一种方式,编译器生成代码时可以对这种结构进行特定的优化,从而产生效率比较高的代码。在java中,编译器根据分支的情况,分别产生tableswitch,lookupswitch两中情况,其中tableswitch适用于分支比较集中的情况,而lookupswitch适用与分支比较稀疏的情况。不过怎么算稀疏,怎么算集中就是编译器的决策问题了,这里不做深入的分析。

简单的找几个例子。

例一:

public class Test {

    public static void main(String[] args) {
        int i = 3;
        switch (i) {
            case 0:
                System.out.println("0");
                break;
            case 1:
                System.out.println("1");
                break;
            case 3:
                System.out.println("3");
                break;
            case 5:
                System.out.println("5");
                break;
            case 10:
                System.out.println("10");
                break;
            case 13:
                System.out.println("13");
                break;
            case 14:
                System.out.println("14");
                break;
	    default:
		System.out.println("default");
                break;
        }


    }
}

反汇编代码可以发现其跳转表的结构:

Code:
   0:	iconst_3
   1:	istore_1
   2:	iload_1
   3:	tableswitch{ //0 to 14
		0: 76;
		1: 87;
		2: 153;
		3: 98;
		4: 153;
		5: 109;
		6: 153;
		7: 153;
		8: 153;
		9: 153;
		10: 120;
		11: 153;
		12: 153;
		13: 131;
		14: 142;
		default: 153 }
   76:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   79:	ldc	#3; //String 0
   81:	invokevirtual	#4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   84:	goto	161
   87:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   90:	ldc	#5; //String 1
   92:	invokevirtual	#4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   95:	goto	161
   98:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   101:	ldc	#6; //String 3
   103:	invokevirtual	#4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   106:	goto	161
   109:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   112:	ldc	#7; //String 5
   114:	invokevirtual	#4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   117:	goto	161
   120:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   123:	ldc	#8; //String 10
   125:	invokevirtual	#4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   128:	goto	161
   131:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   134:	ldc	#9; //String 13
   136:	invokevirtual	#4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   139:	goto	161
   142:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   145:	ldc	#10; //String 14
   147:	invokevirtual	#4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   150:	goto	161
   153:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   156:	ldc	#11; //String default
   158:	invokevirtual	#4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   161:	return

其中的 3:tableswitch{ //0 to 14
0: 76;
1: 87;
2: 153;
3: 98;
4: 153;
5: 109;
6: 153;
7: 153;
8: 153;
9: 153;
10: 120;
11: 153;
12: 153;
13: 131;
14: 142;
default: 153 }
就是跳转表,对于tableswitch指令,这里high为14,low为0,表中共有high-low+1个分支项,当jvm遇到tableswitch指令时,它会检测switch(key)中的key值是否在low~high之间,如果不是,直接执行default部分,如果在这个范围之内,它使用key-low这个项指定的地点跳转。可见,tableswitch的效率是非常高的。

例二:

public class Test2 {

    public static void main(String[] args) {
        int i = 3;
        switch (i) {
            case 3:
                System.out.println("3");
                break;
            case 20:
                System.out.println("20");
                break;
            case 50:
                System.out.println("50");
                break;
            case 100:
                System.out.println("100");
                break;
        }


    }
}

反汇编代码:

Code:
   0:	iconst_3
   1:	istore_1
   2:	iload_1
   3:	lookupswitch{ //4
		3: 44;
		20: 55;
		50: 66;
		100: 77;
		default: 85 }
   44:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   47:	ldc	#3; //String 3
   49:	invokevirtual	#4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   52:	goto	85
   55:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   58:	ldc	#5; //String 20
   60:	invokevirtual	#4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   63:	goto	85
   66:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   69:	ldc	#6; //String 50
   71:	invokevirtual	#4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   74:	goto	85
   77:	getstatic	#2; //Field java/lang/System.out:Ljava/io/PrintStream;
   80:	ldc	#7; //String 100
   82:	invokevirtual	#4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   85:	return

这里使用的是lookupswitch:

3:lookupswitch{ //4
3: 44;
20: 55;
50: 66;
100: 77;
default: 85 }
        这种情况下,必须依次检测每一个项目看是否和switch(key) 中的key匹配,如果遇到匹配的直接跳转,如果遇到比key值大的,执行default,因为3,20,50,100这些项目是按照升序排列的,所以遇到比 key值大的case值后就可以确定后面没有符合条件的值了。另外一点,升序排列也允许jvm实现这条指令时进行优化,比如采用二分搜索的方式取代线性扫描等。

二、对比分析

         switch...case与if...else的根本区别在于,switch...case会生成一个跳转表来指示实际的case分支的地址,而这个跳转表的索引号与switch变量的值是相等的。从而,switch...case不用像if...else那样遍历条件分支直到命中条件,而只需访问对应索引号的表项从而到达定位分支的目的。

三、总结


1、switch和if-else相比,由于使用了Binary Tree算法,绝大部分情况下switch会快一点,除非是if-else的第一个条件就为true.
原理:switch...case会生成一个跳转表来指示实际的case分支的地址,而这个跳转表的索引号与switch变量的值是相等的。从而,switch...case不用像if...else那样遍历条件分支直到命中条件,而只需访问对应索引号的表项从而到达定位分支的目的。
2、switch 使用:
     1)switch(表达式)整型或可以转变为整型的值(byte、short、char和int类型)和枚举类型,long类型不能。
     2)case 后面只能为常数或常量,不能为变量。
     3)执行的过程中,如果遇到break语句,则跳出switch语句。
3、分支较少的时候用if-esle,分支多的时候switch会比较清晰。

猜你喜欢

转载自blog.csdn.net/weixin_40449300/article/details/85016161