一、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会比较清晰。