对于int的switch,jvm是用tableswitch和lookupswitch来实现的,jdk1.7 switch增加了对string的支持,那么底层是如何实现的呢?是否增加了新的指令或是否给某些指令增加了新的含义?
看这样一个程序:
- publicclassTest{
- publicstaticvoidmain(String[]args){
- Stringname="b";
- intvalue=0;
- switch(name){
- case"a":
- value=1;
- break;
- case"b":
- value=2;
- break;
- case"c":
- value=3;
- break;
- case"d":
- value=4;
- break;
- case"e":
- value=5;
- break;
- default:
- value=6;
- }
- System.out.println(value);
- }
- }
javap -c Test得出的结果为:
- publicstaticvoidmain(java.lang.String[]);
- Code:
- 0:ldc#2//Stringb
- 2:astore_1//将"b"赋值给name
- 3:iconst_0//将0入栈
- 4:istore_2//将0赋值给value
- 5:aload_1//将name(即"b")入栈
- 6:astore_3//将name(即"b")赋值给一个编译器生成的变量,记为tmpName(tmpName此时也是"b")
- 7:iconst_m1//将-1入栈
- 8:istore4//将-1赋值给一个编译器生成的变量,记为m1
- 10:aload_3//将tmpName(即"b")入栈
- 11:invokevirtual#3//Methodjava/lang/String.hashCode:()I调用tmpName的hashCode方法("b".hashCode(),得结果98)
- 14:tableswitch{//97to101//根据hashCode的值到不同的分支
- 97:48
- 98:63//这里走到这个分支,跳转到63
- 99:78
- 100:93
- 101:108
- default:120
- }
- 48:aload_3
- 49:ldc#4//Stringa
- 51:invokevirtual#5//Methodjava/lang/String.equals:(Ljava/lang/Object;)Z
- 54:ifeq120
- 57:iconst_0
- 58:istore4
- 60:goto120
- 63:aload_3//从14跳转到了这里,将tmpName(即"b")入栈
- 64:ldc#2//Stringb将"b"入栈
- 66:invokevirtual#5//Methodjava/lang/String.equals:(Ljava/lang/Object;)Z
- //比较tmpName和"b",如果相等,将1入栈,不等将0入栈.这里是相等的,入栈的为1
- 69:ifeq120//从栈顶取出比较结果,如果等于0,就跳到120,如果不等于0就继续下面的指令,这里显然不等于0
- 72:iconst_1//将1入栈
- 73:istore4//将1存储到m1中
- 75:goto120//跳到120
- 78:aload_3
- 79:ldc#6//Stringc
- 81:invokevirtual#5//Methodjava/lang/String.equals:(Ljava/lang/Object;)Z
- 84:ifeq120
- 87:iconst_2
- 88:istore4
- 90:goto120
- 93:aload_3
- 94:ldc#7//Stringd
- 96:invokevirtual#5//Methodjava/lang/String.equals:(Ljava/lang/Object;)Z
- 99:ifeq120
- 102:iconst_3
- 103:istore4
- 105:goto120
- 108:aload_3
- 109:ldc#8//Stringe
- 111:invokevirtual#5//Methodjava/lang/String.equals:(Ljava/lang/Object;)Z
- 114:ifeq120
- 117:iconst_4
- 118:istore4
- 120:iload4//将m1(即73行存进去的1)的值入栈
- 122:tableswitch{//0to4
- 0:156
- 1:161//这里走1这个分支,跳到161
- 2:166
- 3:171
- 4:176
- default:181
- }
- 156:iconst_1
- 157:istore_2
- 158:goto184
- 161:iconst_2//将2入栈
- 162:istore_2//将2存储到value
- 163:goto184//跳转到184进行打印输出
- 166:iconst_3
- 167:istore_2
- 168:goto184
- 171:iconst_4
- 172:istore_2
- 173:goto184
- 176:iconst_5
- 177:istore_2
- 178:goto184
- 181:bipush6
- 183:istore_2
- 184:getstatic#9//Fieldjava/lang/System.out:Ljava/io/PrintStream;
- 187:iload_2
- 188:invokevirtual#10//Methodjava/io/PrintStream.println:(I)V
- 191:return
在这一堆指令中我们发现,jdk1.7并没有新指令来处理string switch,还是继续用lookupswitch和tableswitch两个指令来处理的,也没有扩展这两个指令,它们还是只能处理int。
在11行,我们看到调用了需要switch的string的hashCode方法,并对该hashCode进行switch,并跳转到相应的处理指令。跳到新的指令处我们发现这里(63行)将待switch的变量(name)与case中的值("b")equals了一下。这样做是为了避免不同的string有相同的hashCode,确定equals返回true后,编译器生成了一个处理value的switch,源码里有多少个case编译器生成的tableswitch就有多少个分支,最终会找到相应的处理分支完成string switch的处理。
接下来,看一个不同string hashCode相等的版本:
buzzards与righto的hashCode相等
hierarch与crinolines的hashCode相等
这里选择buzzards和righto
- publicclassTest{
- publicstaticvoidmain(String[]args){
- Stringname="buzzards";
- intvalue=0;
- switch(name){
- case"buzzards":
- value=1;
- break;
- case"righto":
- value=2;
- break;
- default:
- value=6;
- }
- System.out.println(value);
- }
- }
字节码:
- publicstaticvoidmain(java.lang.String[]);
- Code:
- 0:ldc#2//Stringbuzzards
- 2:astore_1
- 3:iconst_0
- 4:istore_2
- 5:aload_1
- 6:astore_3
- 7:iconst_m1
- 8:istore4
- 10:aload_3
- 11:invokevirtual#3//Methodjava/lang/String.hashCode:()I
- 14:lookupswitch{//1
- -931102253:32
- default:59
- }
- 32:aload_3
- 33:ldc#4//Stringrighto
- 35:invokevirtual#5//Methodjava/lang/String.equals:(Ljava/lang/Object;)
- 38:ifeq47
- 41:iconst_1
- 42:istore4
- 44:goto59
- 47:aload_3
- 48:ldc#2//Stringbuzzards
- 50:invokevirtual#5//Methodjava/lang/String.equals:(Ljava/lang/Object;)
- 53:ifeq59
- 56:iconst_0
- 57:istore4
- 59:iload4
- 61:lookupswitch{//2
- 0:88
- 1:93
- default:98
- }
- 88:iconst_1
- 89:istore_2
- 90:goto101
- 93:iconst_2
- 94:istore_2
- 95:goto101
- 98:bipush6
- 100:istore_2
- 101:getstatic#6//Fieldjava/lang/System.out:Ljava/io/PrintStream;
- 104:iload_2
- 105:invokevirtual#7//Methodjava/io/PrintStream.println:(I)V
- 108:return
这里我们看到两个字符串都跳到32行,首先跟"righto"比较,如果不相等则跳到47行比较buzzards。
本文详细解析了JDK1.7中如何通过现有的tableswitch和lookupswitch指令来实现字符串Switch的功能,包括利用hashCode进行初步筛选,再通过equals方法确保准确性。

371

被折叠的 条评论
为什么被折叠?



