开发人员不必为了实现程序正确性而解决 HashMap 中的哈希冲突问题。
这里有几个关键的事情要理解:
- 冲突是散列的固有特征,而且必须如此。可能值的数量(在您的情况下为字符串,但它也适用于其他类型)远远大于整数的范围。
- 哈希的每种用法都有处理冲突的方法,Java 集合(包括 HashMap)也不例外。
- 哈希不参与相等性测试。确实,相等的对象必须具有相等的哈希码,但反之则不然:许多值将具有相同的哈希码。所以不要尝试使用哈希码比较来代替相等。收藏品没有。他们使用散列来选择一个子集合(在 Java 集合世界中称为存储桶),但他们使用 .equals() 来实际检查是否相等。
- 您不仅不必担心冲突会导致集合中的错误结果,而且对于大多数应用程序,您*通常*不必担心性能 - Java 哈希集合在管理哈希码方面做得很好。
- 更好的是,对于您询问的情况(字符串作为键),您甚至不必担心哈希码本身,因为 Java 的 String 类会生成一个非常好的哈希码。大多数提供的 Java 类也是如此。
更多细节,如果你想要的话:
散列的工作方式(特别是在像 Java 的 HashMap 这样的散列集合的情况下,这是您所询问的)是这样的:
HashMap 将您给它的值存储在一个子集合的集合中,称为存储桶。这些实际上是作为链表实现的。这些数量有限:iirc,默认启动 16 个,并且随着您将更多项目放入地图中,数量会增加。桶的数量应该总是多于价值。举一个例子,使用默认值,如果您向 HashMap 添加 100 个条目,则将有 256 个桶。
每个可以用作映射中键的值都必须能够生成一个整数值,称为哈希码。
HashMap 使用这个哈希码来选择一个桶。最终,这意味着取整数值modulo
作为桶的数量,但在此之前,Java 的 HashMap 有一个内部方法(称为hash()
),它调整哈希码以减少一些已知的聚集源。
查找值时,HashMap 会选择存储桶,然后通过链表的线性搜索来搜索单个元素,使用.equals()
.
所以:您不必为了正确性而解决冲突,通常不必担心它们的性能,如果您使用的是本地 Java 类(如 String),则不必担心生成哈希码值。
如果您必须编写自己的哈希码方法(这意味着您编写了一个具有复合值的类,例如名字/姓氏对),事情会变得稍微复杂一些。这里很可能会出错,但这不是火箭科学。首先,要知道这一点:为了确保正确性,您必须做的唯一一件事就是确保相等的对象产生相等的哈希码。所以如果你为你的类写了一个 hashcode() 方法,你还必须写一个 equals() 方法,并且你必须检查每个方法中的相同值。
可以编写一个不好但正确的 hashcode() 方法,我的意思是它会满足“相等的对象必须产生相等的哈希码”约束,但由于有很多冲突,仍然表现得很差。
典型的退化最坏情况是编写一个方法,该方法在所有情况下都简单地返回一个常量值(例如,3)。这意味着每个值都将被散列到同一个桶中。
它仍然可以工作,但性能会下降到链表的性能。
显然,你不会写出这么糟糕的 hashcode() 方法。如果您使用的是不错的 IDE,它能够为您生成一个。由于 StackOverflow 喜欢代码,这里是上面 firstname/lastname 类的代码。
public class SimpleName {
private String firstName;
private String lastName;
public SimpleName(String firstName, String lastName) {
super();
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((firstName == null) ? 0 : firstName.hashCode());
result = prime * result
+ ((lastName == null) ? 0 : lastName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SimpleName other = (SimpleName) obj;
if (firstName == null) {
if (other.firstName != null)
return false;
} else if (!firstName.equals(other.firstName))
return false;
if (lastName == null) {
if (other.lastName != null)
return false;
} else if (!lastName.equals(other.lastName))
return false;
return true;
}
}