map or switch

最近碰到个场景,还蛮有普遍性的,如简单工厂方法,需要依据入参选择不同的业务逻辑进行对应的处理

 

马上想到两个方案

方案一:采用map存放对应入口的处理方法,然后请求进来后经过get就行,map.get(et);

方案二:采用switch语句,

              switch (et) {

              case API3:

                     return api3service;

              case API4:

                     return api4service;

              case API2:

                     return api2service;

              ……

 

if else这种就不予考虑了

 

明显采用map显的更优雅,代码更具可维护性,如果用switch每次需要硬编码

那性能呢?

 

但用map,也可以做些优化处理,比如我发现一些入参在map默认大小为16下,其桶位置发生了碰撞,这样每次get

的时候就需要遍历了,这是不好,当然有两种方案,一是改key值避免碰撞,二是改map大小,让其不发生碰撞,

我采用map大小为64,避免碰撞,当然后面如要继续添加时候,需要关注

经测试,性能可以提升44%,(本机场景,并且这个key在桶的最尾部,也就是需要全部遍历桶全部数据的场景,并且全部预先执行1w次,摒弃了jit对结果的影响)

 

mapget操作,每次需要进行hash,位移操作,&操作,再比较操作,想想就需要很多的指令要执行完才能拿到

 

如果是switch呢?

Switch在编译后,有LookupSwitch TableSwitch,其中TableSwitchO(1)的,LookupSwitch O(log n) 

 

TableSwitch情况

int chooseNear(int i) {
    switch (i) {
        case 0:  return  0;
        case 1:  return  1;
        case 2:  return  2;
        default: return -1;
    }
}

编译后结果

Method int chooseNear(int)
0   iload_1             // Push local variable 1 (argument i)
1   tableswitch 0 to 2: // Valid indices are 0 through 2
      0: 28             // If i is 0, continue at 28
      1: 30             // If i is 1, continue at 30
      2: 32             // If i is 2, continue at 32
      default:34        // Otherwise, continue at 34
28  iconst_0            // i was 0; push int constant 0...
29  ireturn             // ...and return it
30  iconst_1            // i was 1; push int constant 1...
31  ireturn             // ...and return it
32  iconst_2            // i was 2; push int constant 2...
33  ireturn             // ...and return it
34  iconst_m1           // otherwise push int constant -1...
35  ireturn             // ...and return it

 

也就是TableSwitch只要计算一次偏移量,立即就能到case执行,其时间复杂度为O(1) 

 

LookupSwitch

int chooseFar(int i) {
    switch (i) {
        case -100: return -1;
        case 0:    return  0;
        case 100:  return  1;
        default:   return -1;
    }
}

编译后:

Method int chooseFar(int)
0   iload_1
1   lookupswitch 3:
         -100: 36
            0: 38
          100: 40
      default: 42
36  iconst_m1
37  ireturn
38  iconst_0
39  ireturn
40  iconst_1
41  ireturn
42  iconst_m1
43  ireturn

 

也就是LookupSwitch编译后会保证其顺序,并采用二分法查找对应的case,其时间复杂度为O(log n) 

本机,全部预先执行1w次跳过jit的影响,采用mapswitch各执行1亿次,执行时间是两位数的差距,map400msswitch5ms

 

当然测试的场景case都比较少,如果达到1k多个case条件呢? Jit还会把jvm指令缓存吗?,如果不缓存又是另外的情况了

大家可以把eclipse设置Xint,看看屏蔽jit后大量运行的效果

还有switch在什么场景下编译后会是TableSwitch,什么下会是LookupSwitch,毕竟两者的时间复杂度还是有差距

Java应用的性能,还是要详细分析其场景,至于要性能还是代码更优雅,要自己权衡了,呵呵,有更好的方案,还请分享哦

 

参考资料

 

http://stackoverflow.com/questions/10287700/difference-between-jvma-lookupswitch-and-tableswitch

 

http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html#jvms-3.10

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值