![](/img/trans.png)
[英]Why is the switch statement faster than if else for String in Java 7?
[英]Why switch is faster than if
许多 Java 书籍将switch
语句描述为比if else
语句更快。 但是我没有找到任何地方为什么 switch 比 if 更快。
我有一种情况,我必须从两项中选择一项。 我可以使用任何一种使用
switch (item) {
case BREAD:
//eat Bread
break;
default:
//leave the restaurant
}
或者
if (item == BREAD) {
//eat Bread
} else {
//leave the restaurant
}
考虑 item 和 BREAD 是一个常量 int 值。
在上面的例子中,哪个动作更快,为什么?
因为有特殊的字节码可以在很多情况下允许有效的 switch 语句评估。
如果使用 IF 语句实现,您将有一个检查、一个跳转到下一个子句、一个检查、一个跳转到下一个子句等等。 通过开关,JVM 加载要比较的值并遍历值表以查找匹配项,这在大多数情况下更快。
switch
语句并不总是比if
语句快。 它比长长的if-else
语句列表更好地扩展,因为switch
可以基于所有值执行查找。 但是,对于短暂的情况,它不会更快并且可能会更慢。
目前的JVM有两种开关字节码:LookupSwitch和TableSwitch。
switch 语句中的每个 case 都有一个 integer 偏移量,如果这些偏移量是连续的(或大部分是连续的,没有大的间隙)(案例 0:案例 1:案例 2,等等),则使用 TableSwitch。
如果偏移以较大的间隙分布(案例 0:案例 400:案例 93748:等),则使用 LookupSwitch。
简而言之,不同之处在于 TableSwitch 是在恒定时间内完成的,因为可能值范围内的每个值都被赋予了特定的字节码偏移量。 因此,当您给语句偏移 3 时,它知道向前跳转 3 以找到正确的分支。
查找开关使用二进制搜索来查找正确的代码分支。 这在 O(log n) 时间内运行,这仍然很好,但不是最好的。
有关这方面的更多信息,请参阅此处: JVM 的 LookupSwitch 和 TableSwitch 之间的区别?
所以就哪一个最快,使用这种方法:如果您有 3 个或更多案例的值是连续的或几乎连续的,请始终使用开关。
如果您有 2 种情况,请使用 if 语句。
对于任何其他情况,switch 很可能更快,但不能保证,因为 LookupSwitch 中的二进制搜索可能会遇到糟糕的情况。
另外,请记住,JVM 将对 if 语句运行 JIT 优化,尝试将最热分支放在代码中的首位。 这称为“分支预测”。 有关这方面的更多信息,请参见此处: https://dzone.com/articles/branch-prediction-in-java
您的经历可能会有所不同。 我不知道 JVM 没有在 LookupSwitch 上运行类似的优化,但我学会了相信 JIT 优化而不是试图超越编译器。
因此,如果您打算拥有大量数据包 memory 这些天并不是真正的大成本,并且 arrays 相当快。 您也不能依赖 switch 语句来自动生成跳转表,因此更容易自己生成跳转表场景。 正如您在下面的示例中看到的,我们假设最多 255 个数据包。
要获得以下结果,您需要抽象.. 我不会解释它是如何工作的,所以希望您对此有所了解。
如果您需要更多,我将其更新为将数据包大小设置为 255,然后您必须对 (id < 0) || 进行边界检查 (id > 长度)。
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++ 中使用了很多跳转表,所以现在我将展示一个 function 指针跳转表的示例。 这是一个非常通用的示例,但我确实运行了它并且它工作正常。 请记住,您必须将指针设置为 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 语句作为最坏的情况。
即但请记住,它会变得混乱且难以快速管理,而我的其他方法通常更好,但这适用于 arrays 无法解决的情况。 您必须弄清楚您的用例以及每种情况何时最有效。 就像如果您只有几项检查,您就不想使用这些方法中的任何一种。
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 {
}
}
}
}
}
}
}
}
在字节码级别,主题变量仅从运行时加载的结构化 class 文件中的 memory 地址加载到处理器寄存器中,这是在 switch 语句中; 而在 if 语句中,您的代码编译 DE 会生成不同的 jvm 指令,这需要将每个变量加载到寄存器中,尽管使用与前一个 if 语句中相同的变量。 如果您知道用汇编语言进行编码,那么这将是司空见惯的; 虽然 java 编译的 cox 不是字节码,也不是直接的机器码,但其条件概念仍然是一致的。 好吧,我在解释时试图避免更深层次的技术性。 我希望我已经使这个概念变得清晰和神秘。 谢谢你。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.