HashMap中的复合字符串键

时间:2009-10-04 16:54:01

标签: java map key

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

所以为了解决这个问题,根据another帖子中的建议,我计划创建一个将用作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()方法?还需要其他什么吗?

9 个答案:

答案 0 :(得分:12)

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

当然,假设没有一个键可以为空。通常,您可以使用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;
}

答案 1 :(得分:10)

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

答案 2 :(得分:2)

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

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

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

  3. 规则很简单,this page会引导您完成这些规则。

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

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

答案 3 :(得分:2)

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

答案 4 :(得分:2)

这是一个结构良好的等于equals和hashCode的类应该如下所示:(使用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;
}
}

答案 5 :(得分:1)

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

答案 6 :(得分:1)

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

答案 7 :(得分:1)

对于hashCode,您可以使用类似

的内容

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

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

答案 8 :(得分:1)

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