为什么切换比如果快

许多 Java 书籍都将 switch语句描述为比 if else语句更快。但我没有找到任何地方 为什么切换比如果快

例子

我有一个情况,我必须选择任何一个项目的两个。我可以使用任何一种用途

switch (item) {
case BREAD:
//eat Bread
break;
default:
//leave the restaurant
}

或者

if (item == BREAD) {
//eat Bread
} else {
//leave the restaurant
}

考虑到条目和 BREAD 是一个常数整型值。

在上面的例子中,在行动中更快,为什么?

93098 次浏览

因为有一些特殊的字节码可以在很多情况下进行有效的 switch 语句计算。

如果使用 IF 语句实现,则会有一个 check、一个跳转到下一个子句、一个 check、一个跳转到下一个子句等等。通过 switch,JVM 加载要比较的值,并遍历值表以找到匹配,这在大多数情况下更快。

switch语句并不总是比 if语句快。它比一长串 if-else语句的扩展性更好,因为 switch可以基于所有值执行查找。然而,对于一个短的条件,它不会更快,可能会更慢。

因此,如果你计划有数据包内存的负载并不是一个很大的成本这些天和数组是相当快的。您也不能依赖 switch 语句自动生成跳转表,因此自己生成跳转表场景更容易。正如你在下面的例子中看到的,我们假设最大值为255 包裹。

为了得到下面的结果,你需要抽象。.我不会解释这是怎么回事,希望你能理解。

如果需要更多信息,我更新了这个设置,将数据包大小设置为255,这样就需要对(id < 0) | | (id > length)进行边界检查。

Packets[] packets = new Packets[255];


static {
packets[0] = new Login(6);
packets[2] = new Logout(8);
packets[4] = new GetMessage(1);
packets[8] = new AddFriend(0);
packets[11] = new JoinGroupChat(7); // etc... not going to finish.
}


public void handlePacket(IncomingData data)
{
int id = data.readByte() & 0xFF; //Secure value to 0-255.


if (packet[id] == null)
return; //Leave if packet is unhandled.


packets[id].execute(data);
}

编辑因为我在 C + + 中经常使用跳转表现在我将展示一个函数指针跳转表的例子。这是一个非常通用的示例,但我确实运行了它,并且它工作正常。请记住,您必须将指针设置为 NULL,C + + 不会像 Java 那样自动执行此操作。

#include <iostream>


struct Packet
{
void(*execute)() = NULL;
};


Packet incoming_packet[255];
uint8_t test_value = 0;


void A()
{
std::cout << "I'm the 1st test.\n";
}


void B()
{
std::cout << "I'm the 2nd test.\n";
}


void Empty()
{


}


void Update()
{
if (incoming_packet[test_value].execute == NULL)
return;


incoming_packet[test_value].execute();
}


void InitializePackets()
{
incoming_packet[0].execute = A;
incoming_packet[2].execute = B;
incoming_packet[6].execute = A;
incoming_packet[9].execute = Empty;
}


int main()
{
InitializePackets();


for (int i = 0; i < 512; ++i)
{
Update();
++test_value;
}
system("pause");
return 0;
}

还有一点我想提出的是著名的分而治之。因此,我的上述255数组的想法可以减少到不超过8,如果语句作为一个最坏的情况下的场景。

也就是说,但请记住,它会变得混乱,难以快速管理,我的其他方法通常是更好的,但这是利用的情况下,数组只是不会削减它。您必须弄清楚您的用例以及每种情况下什么时候工作最好。正如如果您只有几个检查,就不会使用这两种方法中的任何一种一样。

If (Value >= 128)
{
if (Value >= 192)
{
if (Value >= 224)
{
if (Value >= 240)
{
if (Value >= 248)
{
if (Value >= 252)
{
if (Value >= 254)
{
if (value == 255)
{


} else {


}
}
}
}
}
}
}
}

在字节码级别,主题变量只从结构化的内存地址加载到处理器寄存器中一次。类文件,这是在 switch 语句中; 而在 if 语句中,不同的 jvm 指令是由代码编译 DE 生成的,这要求将每个变量加载到寄存器中,尽管在下一个 if 语句中使用了相同的变量。如果你知道汇编语言编码,那么这将是司空见惯的; 虽然 java 编译的 coxes 不是字节码,也不是直接的机器码,但是这里的条件概念仍然是一致的。 我在解释的时候尽量避免更深层次的技术问题。我希望我已经把这个概念说得很清楚并且不再神秘。谢谢你。

当前的 JVM 有两种开关字节码: LookupSwitch 和 TableSwitch。

Switch 语句中的每种情况都有一个整数偏移量,如果这些偏移量是连续的(或者大部分是连续的,没有很大的间隔)(情况0: 情况1: 情况2,等等) ,则使用 TableSwitch。

如果偏移量以较大的间隔分布(大小写0: 大小写400: 大小写93748: ,等等) ,则使用 LookupSwitch。

简而言之,区别在于 TableSwitch 是在常量时间内完成的,因为可能值范围内的每个值都被赋予了特定的字节码偏移量。因此,当给语句一个偏移量为3时,它知道跳转到前面的3以找到正确的分支。

查找开关使用二进制搜索来查找正确的代码分支。这在 O (log n)时间内运行,这仍然很好,但不是最好的。

有关这方面的更多信息,请参见此处: JVM 的 LookupSwitch 和 TableSwitch 之间的区别?

因此,至于哪一个是最快的,使用这种方法: 如果有3个或3个以上的情况,其值是连续的或接近连续的,请始终使用开关。

如果有两种情况,使用 If 语句。

对于任何其他情况,switch 都比 很有可能快,但是这并不能保证,因为 LookupSwitch 中的二进制搜索可能会遇到不好的情况。

另外,请记住,JVM 将对 if 语句运行 JIT 优化,这些语句将尝试将最热门的分支放在代码的首位。这就是所谓的“分支预测”。有关这方面的更多信息,请参见此处: https://dzone.com/articles/branch-prediction-in-java

你的经历可能会有所不同。我不知道 JVM 是否在 LookupSwitch 上运行类似的优化,但我已经学会了相信 JIT 优化,而不是试图智胜编译器。