为什么切换比如果更快

时间:2011-07-15 10:53:50

标签: java switch-statement

我在java中发现很多书说switch语句比if语句快。但是我没有找到为什么切换比更快的地方。

实施例

我有一种情况我必须选择两个中的任何一项我可以使用以下任何一种方式

switch(item){

case BREAD:
     //eat Bread
break;
default:
    //leave the restaurant

}

或使用if语句,如下所示

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

考虑项目和BREAD是常量int值

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

5 个答案:

答案 0 :(得分:96)

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

如果使用IF语句实现,您将进行检查,跳转到下一个子句,检查,跳转到下一个子句等等。使用switch,JVM加载值进行比较并迭代值表以查找匹配,这在大多数情况下更快。

答案 1 :(得分:30)

switch语句并不总是比if语句快。它比if-else语句的长列表更好地扩展,因为switch可以基于所有值执行查找。但是,对于短期情况,它不会更快,可能会更慢。

答案 2 :(得分:7)

当前的JVM有两种切换字节代码:LookupSwitch和TableSwitch。

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

如果偏移量分散很大(情况0:情况400:案例93748:等),则使用LookupSwitch。

简而言之,差异在于TableSwitch是在恒定时间内完成的,因为可能值范围内的每个值都被赋予特定的字节码偏移量。因此,当您为语句提供3的偏移量时,它知道向前跳3以找到正确的分支。

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

有关详细信息,请参阅此处:Difference between JVM's LookupSwitch and TableSwitch?

至于哪一个最快,请使用以下方法: 如果您有3个或更多个值为连续或接近连续的情况,请始终使用开关。

如果您有2个案例,请使用if语句。

对于任何其他情况,切换很可能更快,但不能保证,因为LookupSwitch中的二进制搜索可能会遇到错误的情况。

另外,请记住,JVM将对if语句运行JIT优化,这些语句将尝试将最热门的分支放在代码中。这被称为"分支预测"。有关详细信息,请参阅此处:https://dzone.com/articles/branch-prediction-in-java

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

答案 3 :(得分:1)

因此,如果您计划拥有大量数据包,那么内存现在并不是很大的成本,并且数组非常快。您也不能依赖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 ++中使用跳转表,我将展示一个函数指针跳转表的示例。这是一个非常通用的例子,但我确实运行它并且它正常工作。请记住,您必须将指针设置为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 {

                              }
                          }
                      }
                  }
             }      
        }
   }
}

答案 4 :(得分:0)

在字节码级别,主题变量仅从运行时加载的结构化.class文件中的内存地址加载到处理器寄存器中一次,这是在switch语句中;而在if语句中,代码编译DE会生成不同的jvm指令,这需要将每个变量加载到寄存器中,尽管使用与下一个if语句之前相同的变量。如果您知道汇编语言的编码,那么这将是司空见惯的;虽然java编译的coxes不是字节码或直接机器代码,但其条件概念仍然是一致的。 好吧,我试着在解释时避免更深入的技术性。我希望我把这个概念弄清楚并揭开神秘面纱。谢谢。

相关问题