HashMap中的复合字符串键

我们将一个String键存储在HashMap中,该HashMap是三个String字段和一个布尔字段的串联。 问题是如果分隔符出现在字段值中,则可以创建重复键。

所以为了解决这个问题,根据另一篇文章中的建议,我打算创建一个将用作HashMap键的键类:

class TheKey { public final String k1; public final String k2; public final String k3; public final boolean k4; public TheKey(String k1, String k2, String k3, boolean k4) { this.k1 = k1; this.k2 = k2; this.k3 = k3; this.k4 = k4; } public boolean equals(Object o) { TheKey other = (TheKey) o; //return true if all four fields are equal } public int hashCode() { return ???; } } 

我的问题是:

  1. 应该从hashCode()返回什么值。 该地图将总共包含约30个值。 在这30个中,有大约10个不同的k1值(一些条目共享相同的k1值)。
  2. 要将此密钥类存储为HashMap密钥,是否只需要覆盖equals()和hashCode()方法? 还需要什么吗?

只是hashCode和equals应该没问题。 hashCode看起来像这样:

 public int hashCode() { int hash = 17; hash = hash * 31 + k1.hashCode(); hash = hash * 31 + k2.hashCode(); hash = hash * 31 + k3.hashCode(); hash = hash * 31 + k4 ? 0 : 1; return hash; } 

当然,假设没有一个键可以为null。 通常,您可以使用0作为上述等式中空引用的“逻辑”哈希码。 需要处理空值的复合相等/哈希码的两种有用方法:

 public static boolean equals(Object o1, Object o2) { if (o1 == o2) { return true; } if (o1 == null || o2 == null) { return false; } return o1.equals(o2); } public static boolean hashCode(Object o) { return o == null ? 0 : o.hashCode(); } 

在本答案开头的哈希算法中使用后一种方法,你最终得到的结果如下:

 public int hashCode() { int hash = 17; hash = hash * 31 + ObjectUtil.hashCode(k1); hash = hash * 31 + ObjectUtil.hashCode(k2); hash = hash * 31 + ObjectUtil.hashCode(k3); hash = hash * 31 + k4 ? 0 : 1; return hash; } 

在Eclipse中,您可以通过Alt-Shift-S h生成hashCode和equals。

请求Eclipse 3.5为您创建哈希码并等于方法:)

这是一个结构良好的equals ans hashCode的equals类应如下所示:(使用intellij idea生成,启用空检查)

 class TheKey { public final String k1; public final String k2; public final String k3; public final boolean k4; public TheKey(String k1, String k2, String k3, boolean k4) { this.k1 = k1; this.k2 = k2; this.k3 = k3; this.k4 = k4; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TheKey theKey = (TheKey) o; if (k4 != theKey.k4) return false; if (k1 != null ? !k1.equals(theKey.k1) : theKey.k1 != null) return false; if (k2 != null ? !k2.equals(theKey.k2) : theKey.k2 != null) return false; if (k3 != null ? !k3.equals(theKey.k3) : theKey.k3 != null) return false; return true; } @Override public int hashCode() { int result = k1 != null ? k1.hashCode() : 0; result = 31 * result + (k2 != null ? k2.hashCode() : 0); result = 31 * result + (k3 != null ? k3.hashCode() : 0); result = 31 * result + (k4 ? 1 : 0); return result; } } 

除非你把它做成超级愚蠢的,否则你的hashCode()的实现并不重要。 你很可能只返回所有字符串哈希码的总和(截断为int),但你应该确保你解决这个问题:

  1. 如果您的哈希代码实现很慢,请考虑在实例中缓存它。 根据您的关键对象的长度以及它们如何与哈希表一起使用时,您可能不希望花费的时间超过必要的时间反复计算相同的值。 如果你坚持使用Jon的hashCode()实现,可能不需要它,因为String已经为你缓存了它的hashCode()。
    然而,这是一个更普遍的建议,因为90年代中期我看到很多开发人员在慢速(甚至更糟,更改)hashCode()实现上受到骚扰。

  2. 创建equals()实现时不要马虎。 你的equals()将无效且有缺陷。 首先,如果对象具有不同的哈希码,则不需要比较值。 如果获得null作为参数,则还应返回false(而不是空指针exception)。

规则很简单, 这个页面将引导您完成它们。

编辑:我还要问一件事……你说“如果分隔符出现在字段值中,则问题是可以创建重复键”。 这是为什么? 如果格式是键+分隔符+键+分隔符+键,那么键中是否有一个或多个分隔符并不重要,除非你真的不太喜欢两个键的组合,在这种情况下你可能应该选择另一个分隔符(在unicode中有很多可供选择)。

无论如何,乔恩在下面的评论中是正确的……不要做“缓存”,直到你certificate这是一件好事。 这总是很好的做法。

你看过hashCode()的规范了吗? 也许这会让你更好地了解函数应返回的内容。

我不知道这是否适合您,但apache commons库为MultiKeyMap提供了一个实现

对于hashCode,你可以使用类似的东西

k1.hashCode()^ k2.hashCode()^ k3.hashCode()^ k4.hashCode()

XOR是熵保留的,这比以前的建议更好地结合了k4的hashCode。 只需从k4获取一位信息意味着如果所有复合键具有相同的k1,k2,k3且只有不同的k4,则哈希码将全部相同,并且您将获得退化的HashMap。

我认为你的主要关注点是速度(根据你原来的post)? 为什么不确保使用一个(少数)字段值中没有出现的分隔符? 然后你可以使用连接创建String键并取消所有这些’key-class’hocus pocus。 闻起来像严重的过度工程对我来说。