为什么 switch 语句效率比 if-else 语句高

switch 语句和 if-else 语句都是条件控制语句,为什么说 switch 语句效率比 if-else 语句高呢?

要回答这个问题,我们先要了解条件控制语句的底层原理是什么。

在计算机底层程序最终都会转换成一条条的指令,CPU 有一个程序计数器(PC),指向下一条要执行的指令,CPU 根据程序计数器的指示加载指令并且执行。

指令大部分是具体的操作和运算,执行完一条指令后,程序计数器会自动指向挨着的下一条指令。

但有一些特殊的指令,称为跳转指令if-else 语句实际上就会转换为这些跳转指令。

跳转指令会修改程序计数器的值,让 CPU 跳到一个指定的地方执行。

跳转指令有两种:

  • 条件跳转:检查某个条件,满足则进行跳转;
  • 无条件跳转:直接进行跳转。

下面一个简单的if语句:

1
2
3
4
5
if (a > b) {
c = 1;
} else {
c = 2;
}

可能被编译为类似这样的汇编代码:

1
2
3
4
5
6
7
8
9
; 假设a在eax,b在ebx,c在ecx
CMP eax, ebx ; 比较a和b
JLE else_branch ; 如果a<=b,跳转到else分支
MOV ecx, 1 ; a>b,执行c=1
JMP end_if ; 跳过else分支
else_branch:
MOV ecx, 2 ; 执行c=2
end_if:
; 继续执行后续代码

这里:

  • JLE(Jump if Less or Equal)是条件跳转指令,根据标志位决定是否跳转
  • JMP 是无条件跳转指令

ifif/elseif/else if/else三元运算符 都会转换为条件跳转和无条件跳转,但 switch 语句不太一样。

switch 的转换和具体系统实现有关:

  • 如果分支比较少,可能会转换为跳转指令;
  • 如果分支比较多,使用条件跳转会进行很多次的比较运算,效率比较低,可能会使用一种更为高效的方式,叫跳转表

跳转表是一个映射表,存储了可能的值以及要跳转到的地址,如下表所示:

条件值 跳转地址
值 1 指令 1 的地址
值 2 指令 2 的地址
值 n 指令 n 的地址

跳转表中的值必须为整数且按大小顺序排序,这样就可以使用高效的二分查找

如果值是连续的,则跳转表还会进行特殊优化,优化为一个数组,值就是数组的下标索引。即使值不是连续的,但数字比较密集,编译器也可能会优化为一个数组型的跳转表,没有的值指向 default 分支。

switch 值的类型可以是 byteshortintcharenumString,其中 byte/short/int 本来就是整数,char 本质上也是整数,而 enum 类型也有对应的整数。

String 用于 switch 时会通过 hashCode() 方法转换为整数,但不同 StringhashCode 可能相同,跳转后会再次根据 String 的内容进行比较判断。

但是 switch 不可以使用 long,因为跳转表值的存储空间一般为 32 位,容纳不下 long