Java 在HashMap中使用字符串键不是个好主意吗?
我知道String类的方法不能保证为不同的String-s生成唯一的哈希代码。我看到了将字符串键放入HashMap-s(使用默认的String hashCode()方法)的许多用法。如果一个mapJava 在HashMap中使用字符串键不是个好主意吗?,java,string,map,hashcode,Java,String,Map,Hashcode,我知道String类的方法不能保证为不同的String-s生成唯一的哈希代码。我看到了将字符串键放入HashMap-s(使用默认的String hashCode()方法)的许多用法。如果一个mapput替换了一个HashMap条目,而该条目以前是用一个真正不同的字符串键放在map上的,那么很多这种用法可能会导致严重的应用程序问题 遇到String.hashCode()为不同的String-s返回相同值的情况的几率有多大?当密钥是字符串时,开发人员如何解决这个问题?我强烈怀疑该方法不能通过查看来确
put
替换了一个HashMap条目,而该条目以前是用一个真正不同的字符串键放在map上的,那么很多这种用法可能会导致严重的应用程序问题
遇到String.hashCode()为不同的String-s返回相同值的情况的几率有多大?当密钥是字符串时,开发人员如何解决这个问题?我强烈怀疑该方法不能通过查看来确定密钥是否相同 肯定会有a的可能性,因此,如果确实存在两个
字符串
s具有从hashCode
返回的相同值的情况,则可以期望调用该方法以确保s真正相等
因此,仅当且仅当返回的值相等,且方法返回的值为true
时,新键String
才会被判断为与HashMap
中已有的键相同的键String
还要补充的是,这种思想对于String
以外的类也是正确的,因为类本身已经有了和方法
编辑
因此,要回答这个问题,不,使用
字符串作为HashMap
的键不是一个坏主意。您正在谈论的是哈希冲突。无论哈希代码的类型是什么,哈希冲突都是一个问题。所有使用hashCode(例如HashMap)的类都可以很好地处理哈希冲突。例如,HashMap可以在每个bucket中存储多个对象
除非您自己调用hashCode,否则不要担心它。哈希冲突虽然很少见,但不会破坏任何东西。开发人员不必在HashMap中解决哈希冲突问题,以实现程序的正确性
这里有两件关键的事情需要理解:
冲突是散列的一个固有特性,必须加以解决。可能值的数量(在您的例子中是字符串,但它也适用于其他类型)远远大于整数的范围。
哈希的每一种用法都有一种处理冲突的方法,Java集合(包括HashMap)也不例外。
在平等性测试中不涉及哈希。相等的对象必须具有相等的哈希代码,这是事实,但反之则不然:许多值将具有相同的哈希代码。因此,不要尝试使用哈希代码比较来代替相等。收藏不会。它们使用散列来选择子集合(在Java集合世界中称为bucket),但它们使用.equals()来实际检查相等性。
您不仅不必担心冲突会在集合中导致不正确的结果,而且对于大多数应用程序,您也*通常*不必担心性能—Java哈希集合在管理哈希代码方面做得相当好。
更好的是,对于您询问的情况(字符串作为键),您甚至不必担心哈希代码本身,因为Java的String类生成了非常好的哈希代码。提供的大多数Java类也是如此。
如果您需要,请提供更多详细信息:
散列的工作方式(特别是在像Java的HashMap这样的散列集合的情况下,这就是您所问的)是:
- HashMap将您给它的值存储在一个称为bucket的子集合集合中。这些实际上是作为链表实现的。其中有一个有限的数量:iirc,默认情况下为16,并且随着您将更多项目放入地图中,数量会增加。存储桶应该总是多于值。举一个例子,使用默认值,如果向HashMap添加100个条目,将有256个bucket
- 可以用作映射中键的每个值都必须能够生成一个整数值,称为hashcode
- HashMap使用此hashcode选择一个bucket。最终,这意味着取整数值
模
桶数,但在此之前,Java的HashMap有一个内部方法(称为hash()
),它调整hashcode以减少一些已知的聚集源
- 在查找值时,HashMap选择bucket,然后使用
.equals()
通过链表的线性搜索来搜索单个元素
因此:您不必为了正确性而绕过冲突,通常也不必为性能而担心冲突,如果您使用的是本机Java类(如String),也不必担心生成哈希代码值
在您必须编写自己的hashcode方法的情况下(这意味着您编写了一个具有复合值的类,如名字/姓氏对),事情会变得稍微复杂一些。这里很有可能出错,但这不是火箭科学。首先,要知道:为了确保正确性,您必须做的唯一一件事就是确保相等的对象产生相等的哈希代码。因此,如果为类编写hashcode()方法,还必须编写equals()方法,并且必须检查每个方法中的相同值
可以编写一个hashcode()方法,该方法不好但正确,我的意思是,它将满足“相等对象必须产生相等的hashcodes”约束,但由于存在大量冲突,其性能仍然很差
规范退化的最坏情况是编写一个方法,该方法在所有情况下仅返回一个常量值(例如,3)。这意味着每个值都将散列到同一个bucket中
它仍然可以工作,但性能会降低
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;
}
}
Hash Lowercase Random UUID Numbers
============= ============= =========== ==============
Murmur 145 ns 259 ns 92 ns
6 collis 5 collis 0 collis
FNV-1a 152 ns 504 ns 86 ns
4 collis 4 collis 0 collis
FNV-1 184 ns 730 ns 92 ns
1 collis 5 collis 0 collis*
DBJ2a 158 ns 443 ns 91 ns
5 collis 6 collis 0 collis***
DJB2 156 ns 437 ns 93 ns
7 collis 6 collis 0 collis***
SDBM 148 ns 484 ns 90 ns
4 collis 6 collis 0 collis**
CRC32 250 ns 946 ns 130 ns
2 collis 0 collis 0 collis
Avg Time per key 0.8ps 2.5ps 0.44ps
Collisions (%) 0.002% 0.002% 0%