这篇是C的,但是感觉原理上说的很清楚 https://www.cnblogs.com/idorax/p/6275259.html
简而言之,就像ifesle和switch本身看上去的那样,ifesle要一条条判断,然后找到符合条件的分支,再进入方法体执行;但是switch像是“直接匹配”到对应的case上一样,没有从第一条开始一条条判断。
实现原理上,ifesle是顺序执行的cmp,je/jne(汇编)实现的,跳转到每个分支方法体的位置。而switch在汇编阶段根据每个case值,算出每个分支方法体的位置,真正执行时,只要把拿到的值当做偏移量加上基地址就可以直接得到分支方法体入口位置。
如果非要做类比的话,判断条件作为key,分支方法体作为value,那么ifelse的策略是做一个二维数组A[N][2],编译阶段将所有ifesle条件放入A[i][0],对应的分支方法体放入A[i][1],每次传入一个key时,遍历数组A[N][0],直至找到key对应的A[i][0],然后A[i][1]就是分支地址,复杂度为O(N);
switch是做了个HashMap,编译阶段直接将每个case的值和对应的方法体塞进HashMap,每次传入一个key,直接找到方法体。复杂度为O(l)。
口说无凭,来试验一哈:
ifelseFunc() :
switchFunc() :
测试:
有点尴尬,好像差不太多,更严重地是,有时switch更慢!!
反汇编可以看到Java编译到class进行了一个改动:
即Java对switch进行了“哈希的哈希”
新建了一个新变量var2,用于跳转到真正的分支方法体。
当然,还有几个小细节:
① Java 1.7以后switch开始支持String作为变量,但是反编译的结果显示:
本质上还是去比较int型的hashCode()。
② 新建了一个var2作为“中间哈希”,其类型为byte,初始值为-1
1)这是否意味着Java的switch最多只支持到127个分支?还是说当大于127可能就用short更大来声明var2...
2)还有,可以看到,每个case分支都将var2重新赋值,而且是从0开始,这从之前那篇博文也可以找到根据,第一个偏移量为0更易操作,比如现在,由于switch判断的变量是String.hashCode(),一般都不会小(31*a[n-1]+31*a[n-2].......),就比如上面例子中虽然是1,2...10但是也变成了:
这样直接拿来计算偏移量肯定很浪费空间,还不如作二次映射,映射到“从0开始,连续的var2"上去,这样在switch(var2)中(真正的分支方法体映射地)var2可以直接拿来计算偏移量。
这是否是Java对switch进行二次哈希的真正原因也不太清楚。。。
3)var2初始值是-1,每个case分支都将var2重新赋值,那default呢:
答案是没有,如果没有找到相应的case,则var2不变,仍为-1。再去看方法体switch(var2):
所以var = -1就是用来标记的,不同于其他情况的var2值。
④ 因为Java对equals()方法和hashCode()规范中,是equals方法同,则hashCode()一定一样;反之不成立。
所以在进行hashcCode()判断之后,还要进行一次equals()方法的判断,都满足才代表真正符合当前的case,才能进入方法体——这也符合判断String是否相等的逻辑——直接equals(),那么问什么不直接switch(equals())呢,因为不能转换为整数啊。。。这switch的实现机制就矛盾。
⑤ 问题来了,因为String的hashCode(0一般都很大,所以二次哈希是可以理解的(如果真是这个原因的话),那么对于普通的数呢?
测试结果:
结果没多大区别。。。
反汇编结果:
可以看到,即使第一个case是10000,编译器也没有作二次哈希,并且,在编译之后,case是按大小排列的。
还可以注意到一个小细节:
ifelseFunc反汇编结果:
① 为每一个if else if else都加上了花括号
② if else if else后面的括号,花括号中间都有一个空格
这是不是一般的编码规范(例如阿里巴巴Java编码规范)要求的原因呢——除了更美观以外,你写的代码都要送给编译器编译的,你所做的越符合编译器规范,编译时重新修改的就越少,编译就越快。