我读到了有关 HashMap 的文章。其中提到:
“不可变性也允许缓存不同键的哈希码,这使得整个检索过程非常快速,并建议使用 Java Collection API 提供的 String 和各种包装类(如
Integer
) 作为HashMap
键。”
我不太明白...为什么?
我读到了有关 HashMap 的文章。其中提到:
“不可变性也允许缓存不同键的哈希码,这使得整个检索过程非常快速,并建议使用 Java Collection API 提供的 String 和各种包装类(如
Integer
) 作为HashMap
键。”
我不太明白...为什么?
String#hashCode
:
private int hash;
...
public int hashCode() {
int h = hash;
if (h == 0 && count > 0) {
int off = offset;
char val[] = value;
int len = count;
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}
由于 String
的内容永远不会改变,该类的创建者选择在计算一次哈希后缓存它。这样,不会浪费时间重新计算相同的值。
String
类,它没有transient
修饰符。 - JeffreyhashCode
时重新计算还要糟糕。 - Jeffrey很简单。由于不可变对象随时间不会改变,因此只需要执行一次哈希码的计算。再次计算将得到相同的值。因此,在构造函数(或懒加载)中计算哈希码并将其存储在字段中是常见的做法。然后hashcode
函数仅返回字段的值,这确实非常快。
在Java中,基本上通过使类不可扩展并且对象中的所有操作理想情况下不改变对象的状态来实现不可变性。如果您查看String的操作(如replace()),它不会更改您正在操作的当前对象的状态,而是提供一个替换字符串的新String对象。因此,如果您将这些对象保持为键,则状态不会更改,因此哈希码也保持不变。因此,在检索期间缓存哈希码将具有性能效益。
将哈希表想象成一个编号的大盒子数组。编号是哈希码,而盒子按编号排序。
如果对象不能更改,则哈希函数始终会产生相同的值。因此,对象将始终保留在它的盒子中。
现在假设一个可变对象。在添加到哈希表后,它被更改了,所以现在它坐在错误的盒子里,就像琼斯夫人嫁给了道先生,现在也叫道,但在许多记录中仍然叫琼斯。
不可变类是不可修改的,这就是为什么它们被用作Map中的键。
举个例子 -
StringBuilder key1=new StringBuilder("K1");
StringBuilder key2=new StringBuilder("K2");
Map<StringBuilder, String> map = new HashMap<>();
map.put(key1, "Hello");
map.put(key2, "World");
key1.append("00");
System.out.println(map); // This line prints - {K100=Hello, K2=World}
你可以看到,由于不经意间对其进行了更改,导致可变类StringBuilder对象K1作为映射键值丢失。如果您使用不可变类作为Map家族成员的键,则不会发生这种情况。
哈希表只有在对象的哈希码在存储在表中时永远不会改变时才能正常工作。这意味着哈希码不能考虑到对象在表中可能发生的任何变化。如果对象的最有趣的方面是可变的,那么就意味着:
哈希码将不得不忽略对象的大部分有趣方面,从而导致许多哈希冲突,或者...
拥有哈希表的代码将不得不确保其中的对象在存储在哈希表中时不会受到可能改变它们的任何影响。