从字节码来分析 Switch 和 if 操作字符串时候的不同

    平时经常会遇到一个需求,针对一个字符串变量的不同值,返回不同的数据或者进行不同的操作。可能我们会选择使用一堆if来判断,也可能会使用Switch来进行操作。那么这两个到底选择使用哪个好呢?我们这次从字节码角度来分析看下。

  • 存在如下两个方法:

    csharp 复制代码
    public int a(String n) {
        switch (n) {
            case "12":
                return 1;
            case "14":
                return 2;
            case "sss":
                return 4;
            default:
                return 5;
        }
    }
    
    public int b(String n){
    
        if (n.equals("12")) {
            return 1;
        } else if (n.equals("14")) {
            return 2;
        } else if (n.equals("sss")){
            return 4;
        } else {
            return 5;
        }
    }

    这两个方法实现的功能一样,都是对于输入的字符串N,根据N的不同值,返回不同的对应的整型。那么我们该优先选择哪个呢?

    先看下字节码。

  • 对于方法 a
vbnet 复制代码
  0 aload_1
  1 astore_2
  2 iconst_m1
  3 istore_3
  4 aload_2
  5 invokevirtual #2 <java/lang/String.hashCode : ()I>
  8 lookupswitch 3
	  1569:  44 (+36)
	  1571:  58 (+50)
	  114195:  72 (+64)
	  default:  83 (+75)
  44 aload_2
  45 ldc #3 <12>
  47 invokevirtual #4 <java/lang/String.equals : (Ljava/lang/Object;)Z>
  50 ifeq 83 (+33)
  53 iconst_0
  54 istore_3
  55 goto 83 (+28)
  58 aload_2
  59 ldc #5 <14>
  61 invokevirtual #4 <java/lang/String.equals : (Ljava/lang/Object;)Z>
  64 ifeq 83 (+19)
  67 iconst_1
  68 istore_3
  69 goto 83 (+14)
  72 aload_2
  73 ldc #6 <sss>
  75 invokevirtual #4 <java/lang/String.equals : (Ljava/lang/Object;)Z>
  78 ifeq 83 (+5)
  81 iconst_2
  82 istore_3
  83 iload_3
  84 tableswitch 0 to 2
	   0:  112 (+28)
	   1:  114 (+30)
	   2:  116 (+32)
	   default:  118 (+34)
  112 iconst_1
  113 ireturn
  114 iconst_2
  115 ireturn
  116 iconst_4
  117 ireturn
  118 iconst_5

    对于上述JVM字节码指令,大体含义流程如下图。

    原理:

markdown 复制代码
1. 底层存在两个表,
2. 一个是基于case中对应的key字符串的hashcode和指令地址映射关系的表,这个数组下标是不连续的,所以是通过指令 lookupswitch 来查找
3. 另一个是保存Switch中每个case返回值的数组,这个数组下标从0开始,数据是连续的,取值时候直接通过下标来取值,所以是 tableswitch
4. 对于java Switch一个字符串,会先根据传入的字符串N的hashCode找到其对应下一步指令地址
5. 找到对应指令地址后,会去将其在tableSwitch中的数组下标存储到对应的操作数栈中
6. 到tableSwitch中通过下标直接取到对应的值。

    通过上述流程原理,可以计算出,Switch一个字符串的耗时是固定的,无论case有多少,操作步骤一样多,所以耗时一样。永远都是4步。

markdown 复制代码
    1. 取hashCod
    2. lookupswitch
    3. equals
    4. tableswitch

    我们接下来看下连续if语句的字节码原理:

makefile 复制代码
 0 aload_1
 1 ldc #3 <12>
 3 invokevirtual #4 <java/lang/String.equals : (Ljava/lang/Object;)Z>
 6 ifeq 11 (+5)
 9 iconst_1
10 ireturn
11 aload_1
12 ldc #5 <14>
14 invokevirtual #4 <java/lang/String.equals : (Ljava/lang/Object;)Z>
17 ifeq 22 (+5)
20 iconst_2
21 ireturn
22 aload_1
23 ldc #6 <sss>
25 invokevirtual #4 <java/lang/String.equals : (Ljava/lang/Object;)Z>
28 ifeq 33 (+5)
31 iconst_4
32 ireturn
33 iconst_5
34 ireturn

    对于上述JVM字节码指令,大体含义流程如下图。

    原理很简单:

    从第一个if开始,依次判断比对if里的条件,只要满足就返回对应结果,否则跳转到下一个指令地址继续比对。

    对于这种连续if,字节码很单纯,就是按照顺序比对if,所以如果if条件越多,对于越靠后的if条件,判断时候花费时间就越多。

    如果对于每个if条件被满足 触发的概率相同,那么总消耗时间就是 1+2+3+..+n = (n + 1)n/2

  • 比较两种方式

    第一种方式耗时永远是4,第二种方式为(n+1)n/2,所以当n<3时,第二种方式总耗时小,当n>=3时候,第一种耗时少。

     同时我们可以看到上图,在AS中,如果if大于等于3个时候,就会提示我们转为Switch,当Switch小于3个时候,就会提示我们转为if。进一步验证了我们的推论。

总结下两者的优劣和使用建议:

    1. 在每个if条件触发概率一样的情况下,以3为界限,小于3个时候,使用连续if判断,大于等于3个时候改为Switch

    2. Switch修饰一个字符串时候,会开辟额外的空间,相当于以空间换效率。

    3. 对于if中条件触发概率不同的情况,触发概率大的if判断,要优先放到前边,这样可以提高效率。

相关推荐
wdfk_prog2 小时前
嵌入式面试真题第 10 题:高优化等级下共享状态可见性、内存模型与系统级同步设计
java·linux·开发语言·面试·职场和发展·架构·c
卓怡学长2 小时前
w255基于springboot仓库管理系统
java·数据库·spring boot·spring·intellij-idea
私人珍藏库4 小时前
[Android] 会计快题库 -财会职称考试刷题学习
android·人工智能·学习·app·软件·多功能
这个DBA有点耶5 小时前
SQL调优进阶:从“优化一条SQL”到“优化一个系统”的思维升级
java·大数据·数据库·sql·程序人生·dba·改行学it
csdn2015_5 小时前
springboot读取配置的方法
java·spring boot·spring
AOwhisky6 小时前
下一代容器来了?Docker 宣布原生支持 WebAssembly
java·运维·docker·容器·rust·wasm
云云只是个程序马喽7 小时前
海外短剧平台搭建方案:私有化源码系统选型|云微短剧系统技术架构拆解
java·php
C137的本贾尼9 小时前
第七篇:消息队列(MQ)——就是个带存储的异步通信管道
java·开发语言·中间件
Flittly9 小时前
【AgentScope Java新手村系列】(19)多模态-图像音频视频
java·spring boot·spring
Javatutouhouduan9 小时前
国内大厂Java面试高频题库(2026突击版)
java·架构师·java面试·java面试题·后端开发·java程序员·java八股文