java switch和if效率_面向JVM编译-switch是如何被编译的?if-else和switch哪个效率更高?...

在正文开始之前,先提出一个经典问题:if-else和switch哪一个效率更高?希望你带着问题学习,并在完成本文学习后整理出你的答案。关于if-else控制结构的编译,可以查看我的历史文章。

概述

JVM在编译switch时使用的是tableswitch和lookupswitch指令。这两个指令的编译结果中都会包含一个长度不固定的表,表中成对存放着case值与当前方法中的指令偏移量,我们暂且把这个表叫做:偏移量表。

tableswitch和lookupswitch指令都会从操作数栈中弹出一个key(即switch关键字后面,圆括号中表达式的值)。如果在所有case值中发现了与key值相匹配的,那么case值对应的指令偏移量将会被携带,用于发起指令调用。

case值紧凑——tableswitch

当switch的所有case值比较紧凑,能够有效表示为偏移量表中的索引时,会被编译为tableswitch,反之被编译为lookupswitch。怎样的一组case值才算是紧凑的?请看示例:

Java代码:

int chooseNear(int i) {

switch (i) {

case 0:

return 0;

case 1:

return 1;

case 2:

return 2;

default:

return 1;

}

}

编译后:

0 iload_1

1 tableswitch 0 to 2

0: 28

1: 30

2: 32

default: 34

28 iconst_0

29 ireturn

30 iconst_1

31 ireturn

32 iconst_2

33 ireturn

34 iconst_1

35 ireturn

如例子中的0、1、2就是紧凑的,紧凑的含义是间距不大,不稀疏。请注意:Java代码中case值的连续性和有序性并不是必须的,接下来把case值改成稀疏非连续的数值再看一看:

Java代码:

int chooseNear(int i) {

switch (i) {

case 0:

return 0;

case 4:

return 1;

case 1:

return 2;

default:

return 1;

}

}

编译后:

0 iload_1

1 tableswitch 0 to 4

0: 36

1: 40

2: 42

3: 42

4: 38

default: 42

36 iconst_0

37 ireturn

38 iconst_1

39 ireturn

40 iconst_2

41 ireturn

42 iconst_1

43 ireturn

当我们把case值改成0、4、1这样的紧凑非连续值时,编译器会自动补充2、3并进行排序,补充的2和3都指向默认的指令偏移量。JVM规定lookupswitch和tableswitch指令的偏移量表必须按照key排序,以便能够实现比线性扫描更加高效的搜索算法,比如二分查找、跳表等.

tableswitch指令格式如下:

tableswitch

<0-3 byte pad>

defaultbyte1

defaultbyte2

defaultbyte3

defaultbyte4

lowbyte1

lowbyte2

lowbyte3

lowbyte4

highbyte1

highbyte2

highbyte3

highbyte4

jump offsets...

指令操作码+0到3字节填充+4字节的默认分支偏移量+4字节的最小case值+4字节的最大case值+若干个有序的4字节分支偏移量。JVM在实现时可以采用跳表的思想,当switch表达式的值位于最小和最大case值范围内时,只需要用表达式值-最小case值,即可得到一个偏移值,通过这个值在jump offsets中直接取到目标分支偏移量。反之,直接获取默认分支偏移量。

0到3字节的填充是为了保证默认分支偏移量在当前方法中的起始地址是4字节的倍数,便于内存对齐,具体填充几个字节由编译器来判断。在现代计算机体系结构中,主存储中的数据会以固定长度数据块的形式被加载到高速缓存中,之后被cpu获取并执行,这个长度可以是4字节/8字节/16字节/32字节/64字节。如果某一条指令的起始地址不是4的倍数,该条指令则需要两次加载才完整,极大地影响了执行效率。

case值稀疏——lookupswitch

当case的值比较稀疏时,编译器并不会自动补充成连续的,因为在空间上会变得低效。

Java代码:

int chooseFar(int i) {

switch (i) {

case 100:

return 0;

case 1:

return 1;

case 2:

return 2;

default:

return 1;

}

}

编译后:

0 iload_1

1 lookupswitch 3

1: 38

2: 40

100: 36

default: 42

36 iconst_0

37 ireturn

38 iconst_1

39 ireturn

40 iconst_2

41 ireturn

42 iconst_1

43 ireturn

JVM并没有给出稀疏和紧凑的标准定义/判断方法,由编译器实现者自行决定。

lookupswitch指令格式如下:

lookupswitch

<0-3 byte pad>

defaultbyte1

defaultbyte2

defaultbyte3

defaultbyte4

npairs1

npairs2

npairs3

npairs4

match-offset pairs...

指令操作码+0到3字节填充+4字节的默认分支偏移量+若干成对出现的key和分支偏移量值。JVM实现者可以使用二分查找的方式找到匹配的key,并获取对应的指令偏移量。lookupswitch中0到3字节填充的目的和tableswitch中一样。

switch支持的数据类型

站在JVM的视角,无论是lookupswitch还是tableswitch,都只支持int类型(划重点:站在JVM的视角,只支持int类型)。满足要求的数据类型包括:byte、char、short、int,前三者会被内部提升为int类型。

再看看JDK的视角,从JDK1.5开始,switch语句支持了枚举类型,从JDK1.7开始又支持了String类型(划重点:是JDK中的switch支持,并不是JVM中的lookupswitch和tableswitch支持)。之所以能够使用枚举和String类型,是因为枚举类有一个ordinal方法可以返回一个int值,String有一个hashCode方法也可以返回一个int值。编译器在编译代码时,自动加入了一些指令,这些指令用于调用ordinal方法或者hashCode方法,从而获取到tableswitch和lookupswitch能够使用的int类型值。另外,Integer类型也是同样的道理,编译器生成了调用intValue()方法的指令,获取到一个int值。

结语

回顾下文章开头的问题:if-else和switch哪一个效率更高?相信你已经可以总结出答案,欢迎讨论。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值