在 Java 中 if/else 与 switch 语句的相对性能差别是什么?

担心我的 Web 应用程序的性能,我想知道哪个“ if/else”或者 switch 语句在性能方面更好?

133133 次浏览

这是微观优化和过早优化,这是邪恶的。相反,我们更担心代码的可读性和可维护性。如果有两个以上的 if/else块粘在一起或其大小是不可预测的,那么您可以高度考虑 switch语句。

或者,你也可以抓取 多态性。首先创建一些界面:

public interface Action {
void execute(String input);
}

你可以静态地或动态地做到这一点:

Map<String, Action> actions = new HashMap<String, Action>();

最后,用这样的代码替换 if/elseswitch(将空指针之类的琐碎检查放在一边) :

actions.get(name).execute(input);

它的 也许吧if/else或者 switch要低很多,但是代码的可维护性至少要好得多。

在讨论 Web 应用程序时,您可以使用 HttpServletRequest#getPathInfo()作为操作键(最终编写一些代码,将 pathinfo 的最后一部分分割成一个循环,直到找到一个操作)。你可以在这里找到类似的答案:

如果您总体上担心 JavaEE Web 应用程序的性能,那么您可能会发现 这篇文章也很有用。除了(微观)优化原始 Java 代码之外,还有其他方面可以提高 更多的性能。

一个 if/else 或开关极不可能成为性能问题的根源。如果存在性能问题,那么应该首先进行性能分析,以确定哪些地方存在性能下降的问题。过早优化是万恶之源!

尽管如此,在 Java 编译器优化中可以讨论 switch 与 if/else 的相对性能。首先请注意,在 Java 中,switch 语句在一个非常有限的域——整数上进行操作。一般情况下,可以按如下方式查看 switch 语句:

switch (<condition>) {
case c_0: ...
case c_1: ...
...
case c_n: ...
default: ...
}

其中 c_0c_1、 ... 和 c_N是作为 switch 语句目标的整数,而 <condition>必须解析为整数表达式。

  • 如果这个集合是“稠密的”——也就是说,(max (c) + 1-min (c))/n > & alpha; ,其中0 < k < & alpha; < 1,其中 k大于某个经验值,就可以生成一个跳表,这是非常有效的。

  • 如果这个集合不是很稠密,但是 n > = & beta; ,二叉查找树可以在 O (2 * log (n))中找到目标,这也是有效的。

对于所有其他情况,switch 语句的效率与等效的 if/else 语句系列完全一样。Α 和 β 的精确值取决于许多因素,并由编译器的代码优化模块决定。

最后,当然,如果 <condition>的域不是整数,那么就有一个开关 声明完全没用。

我完全同意过早优化是应该避免的观点。

但是 JavaVM 确实有特殊的字节码,可以用于 switch ()的。

WM 规格(查找开关桌子开关)

因此,如果代码是性能 CPU 图的一部分,那么可能会有一些性能提升。

根据 Cliff Click 在他2009年的 Java One 演讲 现代硬件速成班:

今天,性能主要由内存访问模式决定。缓存没有占主导地位——内存是新的磁盘。[幻灯片65]

你可以得到他的完整幻灯片 给你

Cliff 给出了一个例子(完成幻灯片30) ,显示即使 CPU 做寄存器重命名、分支预测和 Speculative_execution,它也只能在4个时钟周期内启动7个操作,然后由于两个缓存丢失而不得不阻塞,这需要 300时钟周期才能返回。

所以他说,为了加快你的程序,你不应该关注这种小问题,而应该关注更大的问题,比如你是否正在进行不必要的数据格式转换,比如转换“ SOAP → XML → DOM → SQL → ...”,“通过缓存传递所有数据”。

我记得读到过 Java 字节码中有两种 Switch 语句。(我认为这是在’Java 性能调优’1是一个非常快的实现,它使用 switch 语句的整数值来知道要执行的代码的偏移量。这将要求所有的整数都是连续的并且在一个定义良好的范围内。我猜,使用 Enum 的所有值也属于这一类。

我同意许多其他海报虽然... 它可能为时过早担心这一点,除非这是非常非常热门的代码。

用开关!

我讨厌维护 if-else-block! 测试一下:

public class SpeedTestSwitch
{
private static void do1(int loop)
{
int temp = 0;
for (; loop > 0; --loop)
{
int r = (int) (Math.random() * 10);
switch (r)
{
case 0:
temp = 9;
break;
case 1:
temp = 8;
break;
case 2:
temp = 7;
break;
case 3:
temp = 6;
break;
case 4:
temp = 5;
break;
case 5:
temp = 4;
break;
case 6:
temp = 3;
break;
case 7:
temp = 2;
break;
case 8:
temp = 1;
break;
case 9:
temp = 0;
break;
}
}
System.out.println("ignore: " + temp);
}


private static void do2(int loop)
{
int temp = 0;
for (; loop > 0; --loop)
{
int r = (int) (Math.random() * 10);
if (r == 0)
temp = 9;
else
if (r == 1)
temp = 8;
else
if (r == 2)
temp = 7;
else
if (r == 3)
temp = 6;
else
if (r == 4)
temp = 5;
else
if (r == 5)
temp = 4;
else
if (r == 6)
temp = 3;
else
if (r == 7)
temp = 2;
else
if (r == 8)
temp = 1;
else
if (r == 9)
temp = 0;
}
System.out.println("ignore: " + temp);
}


public static void main(String[] args)
{
long time;
int loop = 1 * 100 * 1000 * 1000;
System.out.println("warming up...");
do1(loop / 100);
do2(loop / 100);


System.out.println("start");


// run 1
System.out.println("switch:");
time = System.currentTimeMillis();
do1(loop);
System.out.println(" -> time needed: " + (System.currentTimeMillis() - time));


// run 2
System.out.println("if/else:");
time = System.currentTimeMillis();
do2(loop);
System.out.println(" -> time needed: " + (System.currentTimeMillis() - time));
}
}

我的 C # 基准测试标准代码

对于大多数 switch和大多数 if-then-else块,我无法想象存在任何明显或重大的与性能相关的问题。

但问题是: 如果您使用的是 switch块,它的使用表明您正在打开从编译时已知的一组常量中获取的值。在这种情况下,如果可以使用具有特定于常数的方法的 enum,那么就根本不应该使用 switch语句。

switch语句相比,枚举提供了更好的类型安全性和更易于维护的代码。枚举可以被设计成这样,如果一个常量被添加到一组常量中,你的代码在没有为新值提供一个特定于常量的方法的情况下就不能编译。另一方面,忘记向 switch块添加新的 case有时只有在运行时才会被捕获,如果您足够幸运地设置了块以引发异常的话。

switchenum常数特异性方法之间的性能应该没有显著差异,但是后者更易读,更安全,更容易维护。

在我的测试中,Windows7中的 ENUM > MAP > SWITCH > IF/ELSE IF性能更好。

import java.util.HashMap;
import java.util.Map;


public class StringsInSwitch {
public static void main(String[] args) {
String doSomething = null;




//METHOD_1 : SWITCH
long start = System.currentTimeMillis();
for (int i = 0; i < 99999999; i++) {
String input = "Hello World" + (i & 0xF);


switch (input) {
case "Hello World0":
doSomething = "Hello World0";
break;
case "Hello World1":
doSomething = "Hello World0";
break;
case "Hello World2":
doSomething = "Hello World0";
break;
case "Hello World3":
doSomething = "Hello World0";
break;
case "Hello World4":
doSomething = "Hello World0";
break;
case "Hello World5":
doSomething = "Hello World0";
break;
case "Hello World6":
doSomething = "Hello World0";
break;
case "Hello World7":
doSomething = "Hello World0";
break;
case "Hello World8":
doSomething = "Hello World0";
break;
case "Hello World9":
doSomething = "Hello World0";
break;
case "Hello World10":
doSomething = "Hello World0";
break;
case "Hello World11":
doSomething = "Hello World0";
break;
case "Hello World12":
doSomething = "Hello World0";
break;
case "Hello World13":
doSomething = "Hello World0";
break;
case "Hello World14":
doSomething = "Hello World0";
break;
case "Hello World15":
doSomething = "Hello World0";
break;
}
}


System.out.println("Time taken for String in Switch :"+ (System.currentTimeMillis() - start));








//METHOD_2 : IF/ELSE IF
start = System.currentTimeMillis();


for (int i = 0; i < 99999999; i++) {
String input = "Hello World" + (i & 0xF);


if(input.equals("Hello World0")){
doSomething = "Hello World0";
} else if(input.equals("Hello World1")){
doSomething = "Hello World0";


} else if(input.equals("Hello World2")){
doSomething = "Hello World0";


} else if(input.equals("Hello World3")){
doSomething = "Hello World0";


} else if(input.equals("Hello World4")){
doSomething = "Hello World0";


} else if(input.equals("Hello World5")){
doSomething = "Hello World0";


} else if(input.equals("Hello World6")){
doSomething = "Hello World0";


} else if(input.equals("Hello World7")){
doSomething = "Hello World0";


} else if(input.equals("Hello World8")){
doSomething = "Hello World0";


} else if(input.equals("Hello World9")){
doSomething = "Hello World0";


} else if(input.equals("Hello World10")){
doSomething = "Hello World0";


} else if(input.equals("Hello World11")){
doSomething = "Hello World0";


} else if(input.equals("Hello World12")){
doSomething = "Hello World0";


} else if(input.equals("Hello World13")){
doSomething = "Hello World0";


} else if(input.equals("Hello World14")){
doSomething = "Hello World0";


} else if(input.equals("Hello World15")){
doSomething = "Hello World0";


}
}
System.out.println("Time taken for String in if/else if :"+ (System.currentTimeMillis() - start));


















//METHOD_3 : MAP
//Create and build Map
Map<String, ExecutableClass> map = new HashMap<String, ExecutableClass>();
for (int i = 0; i <= 15; i++) {
String input = "Hello World" + (i & 0xF);
map.put(input, new ExecutableClass(){
public void execute(String doSomething){
doSomething = "Hello World0";
}
});
}




//Start test map
start = System.currentTimeMillis();
for (int i = 0; i < 99999999; i++) {
String input = "Hello World" + (i & 0xF);
map.get(input).execute(doSomething);
}
System.out.println("Time taken for String in Map :"+ (System.currentTimeMillis() - start));












//METHOD_4 : ENUM (This doesn't use muliple string with space.)
start = System.currentTimeMillis();
for (int i = 0; i < 99999999; i++) {
String input = "HW" + (i & 0xF);
HelloWorld.valueOf(input).execute(doSomething);
}
System.out.println("Time taken for String in ENUM :"+ (System.currentTimeMillis() - start));




}


}


interface ExecutableClass
{
public void execute(String doSomething);
}






// Enum version
enum HelloWorld {
HW0("Hello World0"), HW1("Hello World1"), HW2("Hello World2"), HW3(
"Hello World3"), HW4("Hello World4"), HW5("Hello World5"), HW6(
"Hello World6"), HW7("Hello World7"), HW8("Hello World8"), HW9(
"Hello World9"), HW10("Hello World10"), HW11("Hello World11"), HW12(
"Hello World12"), HW13("Hello World13"), HW14("Hello World4"), HW15(
"Hello World15");


private String name = null;


private HelloWorld(String name) {
this.name = name;
}


public String getName() {
return name;
}


public void execute(String doSomething){
doSomething = "Hello World0";
}


public static HelloWorld fromString(String input) {
for (HelloWorld hw : HelloWorld.values()) {
if (input.equals(hw.getName())) {
return hw;
}
}
return null;
}


}










//Enum version for betterment on coding format compare to interface ExecutableClass
enum HelloWorld1 {
HW0("Hello World0") {
public void execute(String doSomething){
doSomething = "Hello World0";
}
},
HW1("Hello World1"){
public void execute(String doSomething){
doSomething = "Hello World0";
}
};
private String name = null;


private HelloWorld1(String name) {
this.name = name;
}


public String getName() {
return name;
}


public void execute(String doSomething){
//  super call, nothing here
}
}




/*
* http://stackoverflow.com/questions/338206/why-cant-i-switch-on-a-string
* https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html#jvms-3.10
* http://forums.xkcd.com/viewtopic.php?f=11&t=33524
*/

以下连结提供一个很好的解释:
Https://www.geeksforgeeks.org/switch-vs-else/

测试(c + + 17)
1-如果分组
2-如果顺序
3-Goto Array
4-Switch Case-跳转表
Https://onlinegdb.com/su7hnebeg