1. 源码如下,test(int)方法使用switch语法,根据参数a的值返回不同字符串。
public class MySwitchTest {
public String test(int a){
switch (a){
case 1:
return "a";
case 2:
return "b";
case 3:
return "c";
case 5:
return "e";
default:
return "f";
}
}
}
2. javac -g MySwitchTest.java 得到MySwitchTest.class
javap -v -p MySwitchTest.class 可以得到test(int)方法的字节码如下
public java.lang.String test(int);
descriptor: (I)Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=2
0: iload_1
1: tableswitch { // 1 to 5
1: 36
2: 39
3: 42
4: 48
5: 45
default: 48
}
36: ldc #2 // String a
38: areturn
39: ldc #3 // String b
41: areturn
42: ldc #4 // String c
44: areturn
45: ldc #5 // String e
47: areturn
48: ldc #6 // String f
50: areturn
仔细观察,源码中的case分支并没有4,但是字节码中出现了4,这是为什么呢?编译器会对 case 的值做分析,如果 case 的值比较紧凑,中间有少量断层或者没有断层,会采用 tableswitch 来实现 switch-case,有断层的会生成一些虚假的 case 帮忙补齐连续,这样可以将所有的分支地址存储在数组table中,就可以实现 O(1) 时间复杂度的查找:因为 case 已经被补齐为连续的,通过游标就可以一次找到。
寻找分支的逻辑伪代码如下,这段代码时间复杂度就是O(1)
int val = ...; //根据val的值来寻找分支
int pc; //分支所在的索引
if(val < low || val > {
pc = default; //pc指向default分支
} else {
pc = table[val-low]; //从table中寻找分支
}
假如case的值断层严重呢?
public class MySwitchTest {
public String test(int a){
switch (a){
case 100:
return "a";
case 10:
return "b";
case 101:
return "c";
case 1:
return "e";
default:
return "f";
}
}
}
对应的字节码如下
public java.lang.String test(int);
descriptor: (I)Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=2
0: iload_1
1: lookupswitch { // 4
1: 44
10: 47
100: 50
101: 53
default: 56
}
44: ldc #2 // String a
46: areturn
47: ldc #3 // String b
49: areturn
50: ldc #4 // String c
52: areturn
53: ldc #5 // String e
55: areturn
56: ldc #6 // String f
58: areturn
仔细观察,这次没有使用指令tableswitch,而是改为lookupswitch,因为如果继续使用tableswitch,那么需要补齐超级多的case,反而会造成性能下降。
再仔细观察,源码中case分支使无序的,到了字节码中,case值变成有序的了,这样有什么好处呢?因为case值是有序的,在寻找分支时,通过二分法就可以将寻找分支的时间复杂度降为O(logN)
总结,switch语句,在case值比较稀疏时,采用指令lookupswitch,时间复杂度为O(logN),当case值比较紧密时,时间复杂度是O(1),相比于if的O(N)性能要好很多。