简体   繁体   English

HashMap 与 Switch 语句性能对比

[英]HashMap vs Switch statement performance

A HashMap essentially has O(1) performance while a switch state can have either O(1) or O(log(n)) depending on if the compiler uses a tableswitch or lookup switch. HashMap 本质上具有 O(1) 性能,而切换状态可以具有 O(1) 或 O(log(n)),具体取决于编译器是使用 tableswitch 还是查找 switch。

Understandably, if a switch statement is written as such,可以理解,如果 switch 语句是这样写的,

switch (int) {
    case 1:
    case 2:
    case 3:
    case 4:
    default:
}

then it would use a tableswitch and clearly have a performance advantage over a standard HashMap.那么它将使用 tableswitch 并且显然比标准 HashMap 具有性能优势。 But what if the switch statement is sparse?但是如果 switch 语句是稀疏的呢? These would be two examples that I would be comparing:这将是我要比较的两个例子:

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;
}

What would provide more throughput, a lookupswitch or HashMap?什么会提供更多的吞吐量,查找开关或 HashMap? Does the overhead of the HashMap give the lookupswitch an advantage early but eventually tapers off as the number of cases/entries increase? HashMap 的开销是否在早期为查找开关提供了优势,但最终会随着案例/条目数量的增加而逐渐减少?

Edit: I tried some benchmarks using JMH, here are my results and code used.编辑:我使用 JMH 尝试了一些基准测试,这是我使用的结果和代码。 https://gist.github.com/mooman219/bebbdc047889c7cfe612 As you guys mentioned, the lookupswitch statement outperformed the HashTable. https://gist.github.com/mooman219/bebbdc047889c7cfe612正如你们提到的,lookupswitch 语句的性能优于 HashTable。 I'm still wondering why though.我仍然想知道为什么。

The accepted answer is wrong here.接受的答案在这里是错误的。

http://java-performance.info/string-switch-implementation/ http://java-performance.info/string-switch-implementation/

Switches will always be as fast as if not faster than hash maps.开关总是和哈希映射一样快。 Switch statements are transformed into direct lookup tables. Switch 语句被转换为直接查找表。 In the case of Integer values (ints, enums, shorts, longs) it is a direct lookup/jmp to the statement.在整数值(整数、枚举、shorts、longs)的情况下,它是对语句的直接查找/jmp。 There is no additional hashing that needs to happen.不需要进行额外的散列。 In the case of a String, it precomputes the string hash for the case statements and uses the input String's hashcode to determine where to jump.在字符串的情况下,它预先计算 case 语句的字符串哈希,并使用输入字符串的哈希码来确定跳转到哪里。 In the case of collision, it does an if/else chain.在发生碰撞的情况下,它会执行 if/else 链。 Now you might think "This is the same as HashMap, right?"现在你可能会想“这和 HashMap 是一样的,对吧?” But that isn't true.但事实并非如此。 The hash code for the lookup is computed at compile time and it isn't reduced based on the number of elements (lower chance of collision).查找的哈希码是在编译时计算的,它不会根据元素的数量减少(较低的冲突机会)。

Switches have O(1) lookup, not O(n).交换机有 O(1) 查找,而不是 O(n)。 (Ok, in truth for a small number of items, switches are turned into if/else statements. This provides better code locality and avoids additional memory lookups. However, for many items, switches are changed into the lookup table I mentioned above). (好吧,事实上对于少数项目,开关变成了 if/else 语句。这提供了更好的代码局部性并避免了额外的内存查找。但是,对于许多项目,开关变成了我上面提到的查找表)。

You can read more about it here How does Java's switch work under the hood?您可以在此处阅读有关它的更多信息Java 的开关在幕后如何工作?

It depends:这取决于:

  1. If there are a few items |如果有几个项目| fixed items.固定项目。 Using switch if you can ( worst case O(n))如果可以,请使用 switch(最坏情况 O(n))

  2. If there aa lot of items OR you want to add future items without modifying much code ---> Using hash-map ( access time is considered as constant time)如果有很多项目或者你想在不修改太多代码的情况下添加未来的项目--->使用哈希映射(访问时间被视为常数时间)

  3. For your case.对于你的情况。 You should not worry about performance because the different execution time is very small.您不应该担心性能,因为不同的执行时间非常小。 Just focus on readability/maintainability of your code.只关注代码的可读性/可维护性。 Is it worth to optimize a simple case to improve a few nanoseconds?是否值得优化一个简单的案例来改善几纳秒?

TL/DR TL/DR

Base it on code readability and maintainability.它基于代码可读性和可维护性。 Both are cost O(1) and provide almost no difference (though switches generally will be slightly faster).两者的成本都是 O(1) 并且几乎没有区别(尽管开关通常会稍微快一点)。

In this particular case a map would be faster, as a switch returns an address and then must go to that address to identify the return value.在这种特殊情况下,映射会更快,因为开关返回一个地址,然后必须转到该地址以识别返回值。 (A rare case example). (一个罕见的例子)。 If your switch is just calling functions anyways, a Map would also be faster.如果您的 switch 只是调用函数,那么 Map 也会更快。

To make things faster, I would ensure using numeric cases and avoid using strings via constants or enumerators (typescript).为了使事情更快,我将确保使用数字案例并避免通过常量或枚举器(打字稿)使用字符串。

(edited) I confirmed by expectation: How does Java's switch work under the hood? (已编辑)我按预期确认: Java 的开关在幕后如何工作? with switches.带开关。

More detailed answer更详细的回答

In the weeds:在杂草中:

A switch statement will usually be higher performance. switch 语句通常会获得更高的性能。 It creates a lookup table and goto reference and starts at that point.它创建一个查找表并转到引用并从该点开始。 However there are exceptions.不过也有例外。

When you utilize a simple switch such as return map.get(x) vs. switch(1=>'a', 2=>'b', etc).当你使用一个简单的开关时,比如 return map.get(x) vs. switch(1=>'a', 2=>'b', etc)。 That is because the map can directly return the value desired where the switch will stop map the addresses and continue until break or the end is met.那是因为映射可以直接返回所需的值,其中开关将停止映射地址并继续直到中断或结束。

In any event, they should be extremely similar in execution cost.无论如何,它们的执行成本应该非常相似。

Think about maintainability and readability of the code考虑代码的可维护性和可读性

Using a map decouples the data, which can gain the benefit of creating "switch" cases dynamically.使用地图解耦数据,这可以获得动态创建“切换”案例的好处。 More detailed below.下面更详细。

If there are several complex functions/processes you need to handle, it may be easier to read/write if you utilize a map instead.如果您需要处理多个复杂的功能/流程,如果您改用地图,可能更容易读/写。 Especially if the switch statement starts to exceed 20 or 30 options.特别是当 switch 语句开始超过 20 或 30 个选项时。

Personally used case example for maps :个人使用的地图案例示例

I have been utilize the following pattern for flux (Redux/useReducer) in React applications for some time.一段时间以来,我一直在 React 应用程序中使用以下通量模式(Redux/useReducer)。

I create a central map where I map the trigger as the key, and the value is a functional reference.我创建了一个中央映射,将触发器映射为键,值是一个功能引用。 I can then load cases where and when it makes sense.然后我可以在有意义的地方和时间加载案例。

Initially I used this to be able to break the use cases down to reduce file size and group cases of similar function together in a more organized fashion.最初,我使用它来分解用例以减少文件大小并将类似功能的用例以更有条理的方式组合在一起。 Although I later evolved it to be loaded in domains and configured the events and data in a domain hook, like useUser, useFeedback, useProfile, etc...尽管我后来将其发展为在域中加载并在域挂钩中配置事件和数据,例如 useUser、useFeedback、useProfile 等...

Doing so allowed me to create the default state, initialization functions, events, and so forth into a logical file structure, it also allowed me to keep the footprint low until needed.这样做允许我将默认状态、初始化函数、事件等创建到一个逻辑文件结构中,它还允许我在需要之前保持较低的占用空间。

One note to keep in mind要记住的一个注意事项

Using a map does not allow for drop through, though most people consider this code smell anyways.使用地图不允许掉线,尽管大多数人认为这种代码有异味。 At the same time it protects from accidental fall through.同时,它可以防止意外跌落。

在您的情况下,由于您为HashMap使用Integer键,为switch语句使用普通的“ int ”,因此性能最佳的实现将是switch语句,除非通过这部分代码的次数非常高(数十或数十万)。

If I have that kind of example I use Guava ImmutableMaps (sure You can use java 9 builder as well).如果我有那种例子,我会使用 Guava ImmutableMaps(当然你也可以使用 java 9 builder)。

private static final Map<String, String> EXAMPLE = ImmutableMaps.<String, String>>builder()
    .put("a", "100")
    .put("b", "200")
    .build(); 

That way they are immutable and initated only once.这样它们是不可变的,并且只被初始化一次。

Sometimes I use strategy pattern that way:有时我会这样使用策略模式:

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();

Use:用:

EXAMPLE.getOrDefault("a", DEFAULT).execute(); //java 8

About performance just pick readability.关于性能只是选择可读性。 You will thank me later (1 year later) :D.以后你会感谢我(一年后):D。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM