上一篇《if快仍是switch快?解密switch背后的秘密》咱们测试了 if 和 switch 的性能,得出了要尽可能使用 switch 的结论,由于他的效率比 if 高不少,具体缘由点击上文链接查看。java
既然 switch 如此有魅力,那么有没有更好的方法,让 switch 变得更快一些呢?mysql
答案是有的,否则本文就不会诞生了不是?redis
在上篇 if 和 switch 性能对比的文章中有读者问到:String 类型的 switch 性能是否也比 if 高?先说答案,String 类型的条件判断 switch 的性能依旧比 if 好。sql
口说无凭,先举个🌰,测试代码以下:性能优化
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s
@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每一个测试线程一个实例
public class SwitchOptimizeByStringTest {
static String _STR = "Java中文社群";
public static void main(String[] args) throws RunnerException {
// 启动基准测试
Options opt = new OptionsBuilder()
.include(SwitchOptimizeByStringTest.class.getSimpleName()) // 要导入的测试类
.build();
new Runner(opt).run(); // 执行测试
}
@Benchmark
public void switchTest(Blackhole blackhole) {
String s1;
switch (_STR) {
case "java":
s1 = "java";
break;
case "mysql":
s1 = "mysql";
break;
case "oracle":
s1 = "oracle";
break;
case "redis":
s1 = "redis";
break;
case "mq":
s1 = "mq";
break;
case "kafka":
s1 = "kafka";
break;
case "rabbitmq":
s1 = "rabbitmq";
break;
default:
s1 = "default";
break;
}
// 为了不 JIT 忽略未被使用的结果计算,能够使用 Blackhole#consume 来保证方法被正常执行
blackhole.consume(s1);
}
@Benchmark
public void ifTest(Blackhole blackhole) {
String s1;
if ("java".equals(_STR)) {
s1 = "java";
} else if ("mysql".equals(_STR)) {
s1 = "mysql";
} else if ("oracle".equals(_STR)) {
s1 = "oracle";
} else if ("redis".equals(_STR)) {
s1 = "redis";
} else if ("mq".equals(_STR)) {
s1 = "mq";
} else if ("kafka".equals(_STR)) {
s1 = "kafka";
} else if ("rabbitmq".equals(_STR)) {
s1 = "rabbitmq";
} else {
s1 = "default";
}
// 为了不 JIT 忽略未被使用的结果计算,能够使用 Blackhole#consume 来保证方法被正常执行
blackhole.consume(s1);
}
}
复制代码
特殊说明:本文使用的是 Oracle 官方提供的性能测试工具 JMH(Java Microbenchmark Harness,JAVA 微基准测试套件)进行测试的。bash
以上代码测试的结果以下:oracle
从 Score 列(平均完成时间)能够看出 switch 的性能依旧比 if 的性能要高。工具
备注:本文的测试环境为:JDK 1.8 / Mac mini (2018) / Idea 2020.1性能
咱们知道在 JDK 1.7 以前 switch 是不支持 String 的,实际上 switch 只支持 int 类型。测试
在 JDK 1.7 中的 String 类型,其实在编译的时候会使用 hashCode 来做为 switch 的实际值,以上 switch 判断字符串的代码,编译为字节码实际结果以下:
public static void switchTest() {
String var1 = _STR;
byte var2 = -1;
switch(var1.hashCode()) {
case -1008861826:
if (var1.equals("oracle")) {
var2 = 2;
}
break;
case -95168706:
if (var1.equals("rabbitmq")) {
var2 = 6;
}
break;
case 3492:
if (var1.equals("mq")) {
var2 = 4;
}
break;
case 3254818:
if (var1.equals("java")) {
var2 = 0;
}
break;
case 101807910:
if (var1.equals("kafka")) {
var2 = 5;
}
break;
case 104382626:
if (var1.equals("mysql")) {
var2 = 1;
}
break;
case 108389755:
if (var1.equals("redis")) {
var2 = 3;
}
}
// 忽略其余代码...
}
复制代码
知道了 switch 实现的本质,那么优化就变得比较简单了。
从以上的字节码能够看出,若是要优化 switch 只须要把 String 类型变成 int 类型就能够了,这样就剩了每一个 case 中进行 if 判断的性能消耗,最终的优化代码以下:
public void switchHashCodeTest() {
String s1;
switch (_STR.hashCode()) {
case 3254818:
s1 = "java";
break;
case 104382626:
s1 = "mysql";
break;
case -1008861826:
s1 = "oracle";
break;
case 108389755:
s1 = "redis";
break;
case 3492:
s1 = "mq";
break;
case 101807910:
s1 = "kafka";
break;
case -95168706:
s1 = "rabbitmq";
break;
default:
s1 = "default";
break;
}
}
复制代码
此时咱们使用 JMH 进行实际的测试,测试代码以下:
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s
@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s
@Fork(1) // fork 1 个线程
@State(Scope.Thread) // 每一个测试线程一个实例
public class SwitchOptimizeByStringTest {
static String _STR = "Java中文社群";
public static void main(String[] args) throws RunnerException {
// 启动基准测试
Options opt = new OptionsBuilder()
.include(SwitchOptimizeByStringTest.class.getSimpleName()) // 要导入的测试类
.build();
new Runner(opt).run(); // 执行测试
}
@Benchmark
public void switchHashCodeTest(Blackhole blackhole) {
String s1;
switch (_STR.hashCode()) {
case 3254818:
s1 = "java";
break;
case 104382626:
s1 = "mysql";
break;
case -1008861826:
s1 = "oracle";
break;
case 108389755:
s1 = "redis";
break;
case 3492:
s1 = "mq";
break;
case 101807910:
s1 = "kafka";
break;
case -95168706:
s1 = "rabbitmq";
break;
default:
s1 = "default";
break;
}
// 为了不 JIT 忽略未被使用的结果计算,能够使用 Blackhole#consume 来保证方法被正常执行
blackhole.consume(s1);
}
@Benchmark
public void switchTest(Blackhole blackhole) {
String s1;
switch (_STR) {
case "java":
s1 = "java";
break;
case "mysql":
s1 = "mysql";
break;
case "oracle":
s1 = "oracle";
break;
case "redis":
s1 = "redis";
break;
case "mq":
s1 = "mq";
break;
case "kafka":
s1 = "kafka";
break;
case "rabbitmq":
s1 = "rabbitmq";
break;
default:
s1 = "default";
break;
}
// 为了不 JIT 忽略未被使用的结果计算,能够使用 Blackhole#consume 来保证方法被正常执行
blackhole.consume(s1);
}
@Benchmark
public void ifTest(Blackhole blackhole) {
String s1;
if ("java".equals(_STR)) {
s1 = "java";
} else if ("mysql".equals(_STR)) {
s1 = "mysql";
} else if ("oracle".equals(_STR)) {
s1 = "oracle";
} else if ("redis".equals(_STR)) {
s1 = "redis";
} else if ("mq".equals(_STR)) {
s1 = "mq";
} else if ("kafka".equals(_STR)) {
s1 = "kafka";
} else if ("rabbitmq".equals(_STR)) {
s1 = "rabbitmq";
} else {
s1 = "default";
}
// 为了不 JIT 忽略未被使用的结果计算,能够使用 Blackhole#consume 来保证方法被正常执行
blackhole.consume(s1);
}
}
复制代码
以上代码测试的结果以下:
从以上结果能够看出,String 类型的 switch 判断,通过优化以后,性能提高了 2.4 倍,可谓效果显著。
以上的 switch 优化是基于 String 类型的,同时咱们须要注意 hashCode 重复的问题,例如对于字符串“Aa”和“BB”来讲,他们的 hashCode 都是 2112,所以在优化是须要注意此类问题,也就是说咱们使用 hashCode 时,必须保证判断添加的值是已知的,而且最好不要出现 hashCode 重复的问题,若是出现此类问题,咱们的解决方案是在 case 中进行判断并赋值。
咱们本文重点讨论的是 switch 性能优化的方案,固然若是处于性能考虑,咱们还能够使用更加高效的替代方案,例如集合或者是枚举,详见个人另外一篇文章《9个小技巧让你的 if else看起来更优雅》。
经过本文咱们知道 switch 本质上只支持 int 类型的条件判断,即便是 JDK 1.7 中的 String 类型,最终编译的时候仍是会被转化为 hashCode(int)进行判断。但由于编译成字节码后会在 case 中使用 if equals 进行比较,因此性能并不算过高(只比 if 高一点点),所以咱们能够直接把 String 转化成 int 类型进行比较,从而避免在 case 中进行 if equals 判断的性能消耗,这样就大大的提高 switch 的性能,但须要注意的是,有些 key 值的 hashCode 是相同的,所以在优化时须要提早规避。
原创不易,若是以为本文对你有用,请随手点击一个「赞」,这是对做者最大的支持与鼓励,谢谢你。
关注公众号「Java中文社群」回复“干货”,获取 50 篇原创干货 Top 榜。