为什么密钥在Java中是不可变的?

时间:2015-12-03 08:23:34

标签: java hashtable

对这个相当天真的问题道歉,但我相信我自己的回答是天真的。我认为密钥(在HashTables中)是不可变的,因为我们不希望以某种方式意外地改变密钥,因此混乱了HashTable的排序。这是正确的解释吗?如果是这样,它怎么可能更正确?

3 个答案:

答案 0 :(得分:5)

HashTable.put期间对密钥进行哈希处理,并将其值存储在基于哈希的多个存储桶(键值对列表)之一中,例如:

bucket[key.hashcode() % numberOfBuckets].add(key, value)

如果密钥的hashcode在插入后发生了更改,那么它可能会在错误的存储桶中,然后您就无法找到它,并且哈希表会错误地返回任何null { {1}}用于该密钥。

除此之外:了解哈希表的内部工作原理有助于您了解密钥的高质量get功能的重要性。由于糟糕的哈希码函数可能导致桶中密钥的分布不良。由于存储桶只是列表,因此会导致大量线性搜索,从而大大降低了哈希表的有效性。例如这个可怕的哈希码函数将所有内容放在一个桶中,所以它实际上只是一个列表。

hashcode

这也是素数出现在良好哈希码函数中的一个原因,例如:

public int hashcode { return 42; /*terrible hashcode example, don't use!*/ }

答案 1 :(得分:3)

一般的想法是正确的,但不是它的细节。

HashTable中的键不一定是不可变的,它是调用hashCode()(和equals)方法的结果,需要保持不可变并且一致(对于哈希表,行为可预测,即)。

从高层次的角度来看,这是因为哈希表的工作方式:当插入(keyvalue)对时,key内部使用hashCode来找出一个" bucket"价值将放在何处。当value检索key时,会再次计算hashCode,以便找回存储桶。

现在,如果在插入和撤退之间的任何时间点,调用hashCode的结果发生变化,那么"查找存储桶"将与"插入"不同斗,事情不会出现可预测的行为。

总而言之,给定一个看起来像这样的Key对象(两个内部字符串组成objet,但只有一个,在hashCode / equals中考虑partOfHashCode):

public static class Key {
  private String partOfHashCode;
  private String notPartOfHashCode;

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((partOfHashCode == null) ? 0 : partOfHashCode.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;
    Key other = (Key) obj;
    if (partOfHashCode == null) {
      if (other.partOfHashCode != null)
        return false;
    } else if (!partOfHashCode.equals(other.partOfHashCode))
      return false;
    return true;
  }
}

可以这样使用它:

public static void main(String[] args) {

Map<Key, String> myMap = new HashMap<>();
Key key = new Key();
key.partOfHashCode = "myHash";

myMap.put(key, "value");

key.notPartOfHashCode = "mutation of the key, but not of its hash/equals definition";

System.out.println(myMap.get(key));
}

(这会在控制台中记录&#34;值&#34;对象)。

但以这种方式使用它并不好

public static void main(String[] args) {

  Map<Key, String> myMap = new HashMap<>();
  Key key = new Key();
  key.partOfHashCode = "myHash";

  myMap.put(key, "value");

  key.partOfHashCode = "mutation of the hashCode of the key";

  System.out.println(myMap.get(key));
}

(最后一个例子可以在控制台中记录&#34; null&#34;。

有关此主题的更多信息,您还应该阅读hashCode / equals一致性。

答案 2 :(得分:0)

Java中没有HashTable - 密钥是不可变的固有保证。甚至不能保证他们的hashcode保持不变。但是如果添加具有可变hashCode的密钥,则会遇到麻烦。假设您正在插入一个hashCode为1的密钥。然后将其插入对应于1的散列桶中。然后将对象更改为hashCode为2并调用hashMap.get(key) 。当对象仍在hashTable时,系统将在对应于2的存储桶中查找,但在那里找不到它。您甚至无法remove该条目,因为它将无法找到。

tl; dr为了使您的应用程序正常工作HashTable - 密钥需要具有不可变的hashcode,但您必须自己处理这个事实。