[英]HashMap vs Switch statement performance
HashMap 本质上具有 O(1) 性能,而切换状态可以具有 O(1) 或 O(log(n)),具体取决于编译器是使用 tableswitch 还是查找 switch。
可以理解,如果 switch 语句是这样写的,
switch (int) {
case 1:
case 2:
case 3:
case 4:
default:
}
那么它将使用 tableswitch 并且显然比标准 HashMap 具有性能优势。 但是如果 switch 语句是稀疏的呢? 这将是我要比较的两个例子:
HashMap<Integer, String> example = new HashMap<Integer, String>() {{
put(1, "a");
put(10, "b");
put(100, "c");
put(1000, "d");
}};
.
switch (int) {
case 1:
return "a";
case 10:
return "b";
case 100:
return "c";
case 1000:
return "d";
default:
return null;
}
什么会提供更多的吞吐量,查找开关或 HashMap? HashMap 的开销是否在早期为查找开关提供了优势,但最终会随着案例/条目数量的增加而逐渐减少?
编辑:我使用 JMH 尝试了一些基准测试,这是我使用的结果和代码。 https://gist.github.com/mooman219/bebbdc047889c7cfe612正如你们提到的,lookupswitch 语句的性能优于 HashTable。 我仍然想知道为什么。
接受的答案在这里是错误的。
http://java-performance.info/string-switch-implementation/
开关总是和哈希映射一样快。 Switch 语句被转换为直接查找表。 在整数值(整数、枚举、shorts、longs)的情况下,它是对语句的直接查找/jmp。 不需要进行额外的散列。 在字符串的情况下,它预先计算 case 语句的字符串哈希,并使用输入字符串的哈希码来确定跳转到哪里。 在发生碰撞的情况下,它会执行 if/else 链。 现在你可能会想“这和 HashMap 是一样的,对吧?” 但事实并非如此。 查找的哈希码是在编译时计算的,它不会根据元素的数量减少(较低的冲突机会)。
交换机有 O(1) 查找,而不是 O(n)。 (好吧,事实上对于少数项目,开关变成了 if/else 语句。这提供了更好的代码局部性并避免了额外的内存查找。但是,对于许多项目,开关变成了我上面提到的查找表)。
您可以在此处阅读有关它的更多信息Java 的开关在幕后如何工作?
这取决于:
如果有几个项目| 固定项目。 如果可以,请使用 switch(最坏情况 O(n))
如果有很多项目或者你想在不修改太多代码的情况下添加未来的项目--->使用哈希映射(访问时间被视为常数时间)
对于你的情况。 您不应该担心性能,因为不同的执行时间非常小。 只关注代码的可读性/可维护性。 是否值得优化一个简单的案例来改善几纳秒?
它基于代码可读性和可维护性。 两者的成本都是 O(1) 并且几乎没有区别(尽管开关通常会稍微快一点)。
在这种特殊情况下,映射会更快,因为开关返回一个地址,然后必须转到该地址以识别返回值。 (一个罕见的例子)。 如果您的 switch 只是调用函数,那么 Map 也会更快。
为了使事情更快,我将确保使用数字案例并避免通过常量或枚举器(打字稿)使用字符串。
(已编辑)我按预期确认: Java 的开关在幕后如何工作? 带开关。
switch 语句通常会获得更高的性能。 它创建一个查找表并转到引用并从该点开始。 不过也有例外。
当你使用一个简单的开关时,比如 return map.get(x) vs. switch(1=>'a', 2=>'b', etc)。 那是因为映射可以直接返回所需的值,其中开关将停止映射地址并继续直到中断或结束。
无论如何,它们的执行成本应该非常相似。
使用地图解耦数据,这可以获得动态创建“切换”案例的好处。 下面更详细。
如果您需要处理多个复杂的功能/流程,如果您改用地图,可能更容易读/写。 特别是当 switch 语句开始超过 20 或 30 个选项时。
个人使用的地图案例示例:
一段时间以来,我一直在 React 应用程序中使用以下通量模式(Redux/useReducer)。
我创建了一个中央映射,将触发器映射为键,值是一个功能引用。 然后我可以在有意义的地方和时间加载案例。
最初,我使用它来分解用例以减少文件大小并将类似功能的用例以更有条理的方式组合在一起。 尽管我后来将其发展为在域中加载并在域挂钩中配置事件和数据,例如 useUser、useFeedback、useProfile 等...
这样做允许我将默认状态、初始化函数、事件等创建到一个逻辑文件结构中,它还允许我在需要之前保持较低的占用空间。
要记住的一个注意事项
使用地图不允许掉线,尽管大多数人认为这种代码有异味。 同时,它可以防止意外跌落。
在您的情况下,由于您为HashMap
使用Integer
键,为switch
语句使用普通的“ int
”,因此性能最佳的实现将是switch
语句,除非通过这部分代码的次数非常高(数十或数十万)。
如果我有那种例子,我会使用 Guava ImmutableMaps(当然你也可以使用 java 9 builder)。
private static final Map<String, String> EXAMPLE = ImmutableMaps.<String, String>>builder()
.put("a", "100")
.put("b", "200")
.build();
这样它们是不可变的,并且只被初始化一次。
有时我会这样使用策略模式:
private static final Map<String, Command> EXAMPLE = ImmutableMaps.<String, String>>builder()
.put("a", new SomethingCool())
.put("b", new BCool())
.build();
private static final Command DEFAULT= new DefCommand();
用:
EXAMPLE.getOrDefault("a", DEFAULT).execute(); //java 8
关于性能只是选择可读性。 以后你会感谢我(一年后):D。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.