switch case_switchcase函数

switch case_switchcase函数语言的底层就是算法,所以switch-case的底层也是算法: 数组和二分查找。 switch-case是一个条件语句,也就是说:

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

实现原理

语言的底层就是算法,所以switch-case的底层也是算法: 数组和二分查找。

switch-case是一个条件语句,也就是说: 如果满足条件,那么就执行对应的指令,也就是: 找条件!那么就是查找!也就是算法里的查找!

那么为什么是数组和二分查找呢?

其实switch-case的状态值只能存放一个int的大小,比如byte,char,shor,int等。如果大于一个int的大小就不可以,比如long,double等。那么String为什么也可以呢?因为switch(String)会取String对应的hashcode,而hashcode正好是一个int的大小。

正是因为状态值是一个int的大小,所以可以对状态值进行排序,如果状态值是紧凑的,那么就会将状态值优化为tableswitch(可以理解为数组),查找效率就是O(1)的;如果状态值是分散的,那么就会优化为lookupswitch,然后使用二分查找来找条件。

现在我们来证明,假设有如下代码:

public void test(int a){
    int b = 10;
    switch(a) {
        case 1: 
            b = 1;
        break;
        case 2: 
            b = 2;
        break;
        case 3: 
            b = 3;
        break;
        default:
            b = 4;
        break;
    }
}

我们使用javac指令得到Test.class对象,然后使用javap -verbose Test.class得到字节码如下(这里只粘出关键部分):

public void test(int);
    descriptor: (I)V // 表示函数参数类型是int,返回类型是void
    flags: (0x0001) ACC_PUBLIC // 表示函数是public的
    Code:
      stack=1, locals=3, args_size=2 // 栈深度为1,局部变量表为2,参数个数为2
         0: bipush        10
         2: istore_2
         3: iload_1
         4: tableswitch   { // 1 to 3 看这里,优化成了tableswitch
                       1: 32 // 表示case到1就执行第32行字节码
                       2: 37
                       3: 42
                 default: 47
            }

可以看到,我们确实是优化成了tableswitch,因为我们case的值是”连续的”,连续的肯定是紧凑的,那么紧凑必须是连续的吗?非也!我们修改代码如下:

public void test(int a){
    int b = 10;
    switch(a) {
        case 1: 
            b = 1;
        break;
        case 3: 
            b = 3;
        break;
        case 5: 
            b = 5;
        break;
        default:
            b = 4;
        break;
    }
}

现在,我们的case值是1、3、5,不连续了,我们继续javap一下来看字节码:

public void test(int);
    descriptor: (I)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=3, args_size=2
         0: bipush        10
         2: istore_2
         3: iload_1
         4: tableswitch   { // 1 to 5 这里自动补上了2和4
                       1: 40
                       2: 55 // 执行第55行字节码
                       3: 45
                       4: 55 // 也是执行第55行字节码
                       5: 50
                 default: 55 // 也是执行第55行字节码
            }

可以看到,这里自动补上了2和4,变为连续的了,为的就是凑齐一个数组,然后可以直接根据下标进行查找定位,从而达到O(1)的时间复杂度。而且2和4跟default一样,都执行第55行字节码,因为本来就没有2和4,就按default处理。

现在,让我们修改case 3为case 100,其他不变,然后看字节码:

public void test(int);
    descriptor: (I)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=3, args_size=2
         0: bipush        10
         2: istore_2
         3: iload_1
         4: lookupswitch  { // 3 变为lookupswitch了
                       1: 40
                       5: 51
                     100: 45
                 default: 56
            }

我们发现,现在变为lookupswitch了,并且还发现,case的值按照顺序排序了,这是为了使用二分查找。

因为从5到100断层比较严重,我们不可能为了O(1)的时间复杂度去插入95个值,可以看出JVM在时间和空间还是衡量的比较人性化的。

这里需要注意两点:

  • tableswitch的生成的条件是”紧凑”而不是”数值小”,比如10001,10002,10003也会优化成tableswitch,而1,100,200就只能优化成lookupswitch。
  • 如果case的值是String,那么会优化成lookupswitch,因为hashcode一般都是比较分散的(避免碰撞嘛)。

优化

明白了上述原理,我们怎么在代码中优化呢?很简单!

  • 第一: 我们尽可能的让case的值连续,或者紧凑,千万别为了装x而xjb写,而且尽量不使用String(因为hashcode本来就比较分散)

  • 第二: 如果业务比较复杂,或者出于拓展性考虑,那么可以使用hashmap来优化,毕竟hashmap的时间复杂度大部分情况都是O(1)的,而且将来修改也满足OCP

比如上述代码我们就可以这样用hashmap来优化:

// 首先定义一个Action接口:
public interface Action {
    int action();
}

// 然后初始化
private HashMap<Integer, Action> actions = new HashMap<>();
{
    actions.put(1, () -> 1); // case 1
    actions.put(3, () -> 3); // case 3
    actions.put(5, () -> 5); // case 5
}

// test函数就可以简写为如下:
public void test(int a) {
    int b = actions.get(a).action();
}

将来要添加case或删除case,只要在actions中添加或删除值就行,美滋滋。

综上,我们可以知道:

  • 1 建议优先使用hashmap来处理switch-case这种复合条件的场景。
  • 2 如果条件比较简单,或者不必考虑拓展性,可以使用swtich-case,但是建议 非必要不使用 String来作为case值,而且建议case值一定要紧凑。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/13573.html

(0)

相关推荐

  • MySQL日志简介「建议收藏」

    MySQL日志简介「建议收藏」一.MySQL日志简介 二.错误日志 作用: 记录mysql数据库的一般状态信息及报错信息,是我们对于数据库常规报错处理的常用日志。 默认位置: $MYSQL_HOME/data/ 开启方式: (My

    2022-12-18
    139
  • Flutter控件– CheckBox 和 CheckboxListTile

    Flutter控件– CheckBox 和 CheckboxListTile一。CheckBox(勾选框) 二。CheckboxListTile(有标题的Checkbox)

    2023-11-10
    128
  • 记mysql中delete操作中使用in不会走索引的问题[通俗易懂]

    记mysql中delete操作中使用in不会走索引的问题[通俗易懂]目前负责的一个项目,需要维护一个电话号码对比库,表名为phone_bak1,以下称为a表,量级为3000万条。还有另外一张表存储电话白名单,表名为phone_delete,以下称为b表,量级为3000

    2023-02-05
    153
  • 前端该如何准备数据结构和算法?[亲测有效]

    前端该如何准备数据结构和算法?[亲测有效]据我了解,前端程序员有相当一部分对“数据结构”和“算法”的基础概念都不是很清晰,这直接导致很多人在看到有关这部分的内容就会望而却步。 实际上,当你了解了“数据结构”和“算法”存在的真正意义,以及一些实际的应用场景,对它有了一个整体的认知之后,你可能会对它产生强烈的兴趣。当然,它…

    2023-07-21
    115
  • 华为云GaussDB数据库荣获国际CC EAL4+级别认证[亲测有效]

    华为云GaussDB数据库荣获国际CC EAL4+级别认证[亲测有效]摘要:近日,华为云GaussDB企业级分布式数据库内核正式通过了全球知名独立认证机构欧洲SGS Brightsight实验室的安全评估,获得全球权威信息技术安全性评估标准CC EAL4+级别认证。 本

    2023-06-11
    135
  • 如何使用Anaconda删除虚拟环境

    如何使用Anaconda删除虚拟环境在开发过程中,我们通常需要使用虚拟环境来隔离不同的项目依赖,这样可以避免不同项目之间的依赖冲突。但当不再需要这些虚拟环境时,我们需要将其删除以释放空间。本文将介绍如何使用Anaconda删除虚拟环境。

    2024-05-15
    77
  • golang 性能调优_benchmark测反应

    golang 性能调优_benchmark测反应Golang性能基准测试可以帮助开发人员比较不同的实现方式对性能的影响,以便优化程序,本文就来讲解一下如何使用Golang的性能基准测试功能。

    2023-07-08
    132
  • 【JDBC】笔记(2)-[通俗易懂]

    【JDBC】笔记(2)-[通俗易懂]1、需求:模拟用户登录功能的实现;2、业务描述:程序运行的时候,提供输入的入口,可以让用户输入用户名和密码;用户输入用户名和密码后,提交信息,java程序收集到用户信息…..

    2023-05-03
    152

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注