21

我一直试图关注良好的性能和干净的代码。

我很难理解拥有一个包含 150 个字符的键的 HashMap 是否合理。

  • HashMap 键的长度有不成文的规律吗?
  • 让我们说 150 个字符的字符串键被认为是不好的做法吗?
  • 会影响性能吗?多长?
4

4 回答 4

17

不是真的,150 个字符的字符串计算一个 for 是相对微不足道的hashCode

话虽如此,在这种情况下,我建议您对其进行测试!

创建一个填充 HashMap 的例程,例如,在此处插入一个大小,该大小代表您的使用场景随机值,其中 5 个字符串作为键。测量需要多长时间。然后对 15 个字符键执行相同的操作,看看它是如何缩放的。

此外,Java 中的字符串是不可变的,这意味着hashCode可以为存储在字符串常量池中的每个字符串进行缓存,并且在对同一个字符串对象调用 hashCode 时不需要重新计算。

这意味着尽管您在创建地图时计算了更大的哈希码,但在访问时,其中许多已经被预先计算和缓存,使得原始字符串的大小变得更不相关。

于 2013-05-12T10:59:08.587 回答
9

HashMap 键的长度有不成文的规律吗?

如果有,也是不言而喻的。我会在分析器中衡量您的用例,只担心您可以衡量的问题,而不是您可以想象的可能是问题的事情。

让我们说 150 个字符的字符串键被认为是不好的做法吗?

我对此表示怀疑。

会影响性能吗?多长?

一切都会影响性能,通常是小到无关紧要,有时甚至是衡量。问题应该是;你需要 150 个字符的键吗?如果你这样做,然后使用它们。


有一种奇特的情况,其中添加 hashCode() 为零的字符串是一个坏主意。这是因为在 Java 1.0 到 6 中没有优化 hashCode 为零的用例,并且可以预测为拒绝服务攻击。Java 7 通过使用次要的、不可预测的哈希码来解决这个问题。

为什么 String 的 hashCode() 不缓存 0?

于 2013-05-12T10:55:53.293 回答
5

长答案:快速查看源代码会String::hashCode()发现哈希在第一次调用后被缓存。同时,String::equals()如果字符串相等但不相同(即,equals()为真但==为假,因为它们分配在不同的地址),则为 O(n)。

因此,您将看到对性能的影响:

  • HashMap在对函数的调用中传递从未散列过的字符串。但是,生成大量新字符串本身会影响性能。

  • 调用HashMap::get()HashMap::put()使用与 HashMap 中已有键相等的字符串键(因为如果键不在集合中,则很可能只调用 hashCode()。但如果是,equals() 将比较所有字符,直到它确定字符串相等)。前提是传递给这些函数的字符串与 HashMap 中已经存在的对象不同,因为在这种情况下equals()速度非常快。

  • 此外,字符串字面量、字符串常量和手动intern()'d 字符串加入字符串常量池,其中所有“相等”的字符串都是具有相同地址的同一个对象。因此,如果专门使用此类字符串,hashCode并且equals速度非常快。

当然,除非您在一个紧密的循环中执行上述操作(因为 150 个字符并不长并且 hashCode() 和 equals() 都很有效),否则性能影响根本不会明显。

简短的回答:基准。

于 2013-05-12T11:06:56.737 回答
4

首先,没有“不成文的规定”。如果从算法的角度来看,长字符串作为键有意义,请使用它们。如果分析表明存在问题,则进行优化。

那么长字符串如何影响哈希表的性能呢

  • 长字符串比短字符串占用更多内存,这可能会导致明显更长的垃圾收集时间,以及与硬件内存缓存、TLB 和(潜在的)物理内存页面争用相关的其他次要性能影响。

  • String 的哈希码算法使用字符串的所有字符,因此其成本与字符串长度成正比。缓存字符串哈希码的事实减轻了这种情况。(第二次和后续调用hashcodeString 时,您将获得缓存的值。)但是,只有在使用相同的 String 对象作为键执行多个哈希表操作时,这才有帮助(此处)。

  • 当您遇到哈希冲突时,哈希表会在搜索选定的哈希链时回退到使用String.equals()比较键。在最坏的情况下(例如当字符串是equal但不是时==),String.equals()涉及比较两个字符串的所有字符。

如您所见,这些效果将特定于实际应用,因此很难预测。因此,“不成文的规则”不太可能有帮助。

于 2013-05-12T11:18:54.123 回答