Java switch及tableswitch、lookupswitch介绍

13 篇文章 0 订阅

在Java中switch的形式:

switch(key)
{
    case num1:
        //可以加入自己的业务逻辑
        break;

    case num2:
        //可以加入自己的业务逻辑
        break;

    default:
        //可以加入自己的业务逻辑
        break;
}

上述代码中switch(key)中的key可以是什么类型?参考文章
(1)最早时,只支持int、char、byte、short这样的整型的基本类型或对应的包装类型Integer、Character、Byte、Short常量,其实包装类型最终也会经过拆箱为基本类型,本质上还是只支持基本类型
(2)JDK1.5开始支持enum,原理是给枚举值进行了内部的编号,进行编号和枚举值的映射
(3)1.7开始支持String,但不允许为null,原理是借助 hashcode( ) 来实现。
(4)此篇文章重点讲述 tableswitch  和 lookupswitch

一、首先看下述代码

package com.Ycb.jvm;

public class SwitchTest {

    /**
     * 代码片段一
     *
     * @param i
     */
    public void switch1(int i) {
        int j;
        switch (i) {
            case 1:
                j = 1;
                break;
            case 2:
                j = 2;
                break;
            case 5:
                j = 3;
                break;
            default:
                break;
        }
    }

    /**
     * 代码片段二
     *
     * @param i
     */
    public void switch2(int i) {
        int j;
        switch (i) {
            case 1:
                j = 1;
                break;
            case 5:
                j = 2;
                break;
            case 10000:
                j = 3;
                break;
            default:
                break;
        }
    }
}

上述代码的逻辑几乎一模一样。主要就是case 中 的值不太一样,方法switch1()中几乎是连续的,方法switch2()中的跨度比较大,不连续。我们可以看下生成的字节码

方法switch1()对应的字节码(有省略):

public void switch1(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=3, args_size=2
         0: iload_1
         1: tableswitch   { // 1 to 5
                       1: 36
                       2: 41
                       3: 51
                       4: 51
                       5: 46
                 default: 51
            }
        36: iconst_1
        37: istore_2
        38: goto          51
        41: iconst_2
        42: istore_2
        43: goto          51
        46: iconst_3
        47: istore_2
        48: goto          51
        51: return

方法switch2()对应的字节码(有省略):

public void switch2(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=3, args_size=2
         0: iload_1
         1: lookupswitch  { // 3
                       1: 36
                       5: 41
                   10000: 46
                 default: 51
            }
        36: iconst_1
        37: istore_2
        38: goto          51
        41: iconst_2
        42: istore_2
        43: goto          51
        46: iconst_3
        47: istore_2
        48: goto          51
        51: return

从上面的字节码中可以看出方法switch1()是使用tableswitch来辅助实现switch,如下

1: tableswitch   { // 1 to 5
                       1: 36
                       2: 41
                       3: 51
                       4: 51
                       5: 46
                 default: 51
            }

(1)对于tableswitch指令,这里high为5,low为1,表中共有high-low+1个分支项,当jvm遇到tableswitch指令时,它会检测switch(key)中的key值是否在low~high之间,如果不是,直接执行default部分,如果在这个范围之内,它使用key-low这个项指定的地点跳转。可见,tableswitch的效率是非常高的。感觉这个有点类似于数组,我们知道数组根据下标去查找元素的时间复杂度是O(1),效率是非常高的。

(2)我们看下方法switch1()的源码,case 分支只有值 1 、2 和 5,并没有 3 和 4。但是为了便于利用数组的连续的特性,添加了 3 和 4,所以这也就解释了,当case中的值不连续时不宜使用tableswitch的原因,会导致很多的空间浪费,比如方法switch2()中的case 分支 的值有1 、5 和 10000,如果继续使用tableswitch,就需要补充上 2、3、4 和 6 ~9999中间的值,而这些其实很多都是无效的值,会导致浪费很多的空间。所以针对 方法 switch2()使用lookupswitch

方法switch2()是使用lookupswitch,如下

1: lookupswitch  { // 3
                       1: 36
                       5: 41
                   10000: 46
                 default: 51
            }

switch是控制选择的一种方式,编译器生成代码时可以对这种结构进行特定的优化,从而产生效率比较高的代码。在java中,编译器根据分支的情况,分别产生tableswitch和lookupswitch两中情况,其中tableswitch适用于分支比较集中的情况,而lookupswitch适用与分支比较稀疏的情况。不过怎么算稀疏,怎么算集中就是编译器的决策问题了。

JVM规范中有如下一段内容:
       Compilation of switch statements uses the tableswitch and lookupswitch instructions. The tableswitch instruction is used when the cases of the switch can be efficiently represented as indices into a table of target offsets. The default target of the switch is used if the value of the expression of the switch falls outside the range of valid indices.Where the cases of the switch are sparse, the table representation of the tableswitch instruction becomes inefficient in terms of space. The lookupswitch instruction may be used instead.

       The Java virtual machine specifies that the table of the lookupswitch instruction must be sorted by key so that implementations may use searches more efficient than a linear scan. Even so, the lookupswitch instruction must search its keys for a match rather than simply perform a bounds check and index into a table like tableswitch. Thus, a tableswitch instruction is probably more efficient than a lookupswitch where space considerations permit a choice.

if....else 与 switch....case的初步对比分析

我们把方法switch1()的代码改写下,改为使用if....else实现,如下:

public void ifSwitch(int i) {
        int j;
        if (i == 1) {
            j = 1;
        } else if (i == 2) {
            j = 2;
        } else if (i == 5) {
            j = 3;
        }
    }

我们查看下字节码:

public void ifSwitch(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=2
         0: iload_1
         1: iconst_1
         2: if_icmpne     10
         5: iconst_1
         6: istore_2
         7: goto          27
        10: iload_1
        11: iconst_2
        12: if_icmpne     20
        15: iconst_2
        16: istore_2
        17: goto          27
        20: iload_1
        21: iconst_5
        22: if_icmpne     27
        25: iconst_3
        26: istore_2
        27: return

我们可以看到 if....else 是使用if_icmpne指令和goto来配合实现的。当传入的值在if分支中比较靠后或者传入的值压根不在if分支中,就需要执行前面的很多条if_icmpne比较,而使用switch....case 的话,如果使用 tableswitch 那直接执行O(1)的时间复杂度就可以直接执行goto跳转,如果采用的lookupswitch,时间复杂度也小很多。if ..else走逻辑判断时,每条if语句都独立需要加载,都要走一遍判断。这就是耗时的机制问题了。switch..case 根据一个值进行多路分支,只做一次计算,然后将表达式的值与每个case的值比较,进而选择哪一个case语句块。

但是当if 中的 条件是个范围,比如if(key>1 && key<2)这种形式,此时switch就无能为力了,switch只能使用在等值的场景。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值