字符串作为hashmap中的键

我在过去一小时内阅读了很多post,但我仍然不太清楚使用不可变对象作为Hashmap中的键的概念。 我有一个将其键作为String的hashmap。 hashmap中的值是MyStore,其中MyStore表示有关我拥有的商店的信息。 String表示地址。 在我的代码中,我的逻辑是,我首先在地图中查找该键,如果存在 – >获取其值,如果它不存在则将其放入hashmap中。 我的经理告诉我,密钥将来会改变,那就是我的商店的地址将来会改变。 他说在那种情况下,我首先检查密钥是否存在的逻辑不起作用。 我不明白他的意思。 我想非常清楚地理解以下几点 –

  1. hashmap的可变键和不可变键之间的区别。
  2. 如果使用可更改的不可变密钥会发生什么? – 我知道这没有意义,但我想清楚地了解我的经理在这里谈论的内容。
  3. 有些post谈论字符串如果用作哈希映射中的键,则将其哈希码缓存 – 这是什么意思?
  4. 如果让我说我在我的hashmap中使用可变对象作为实现hashcode和equals的键,那么它会起作用吗? 我假设它会,因为如果密钥更改,contains方法将查看密钥是否存在。 如果它不存在,它将输入该条目,以便您将来可以获得它。

如果之前已经讨论过,我并不是要创建一个重复的post。 如果我错过了阅读有关我所有问题答案的post,请指出。 如果没有,请以外行的方式解释我的上述问题,以便将来对其他读者有用:)。 随意编辑我的post的主题,以便将来如果有人有类似的问题,他们直接降落在这里:)

第一:HashMap是如何工作的?

基本上它有一个数组,当你在地图中放置一个键值对时,它存储在数组中的一个位置。 数组中的位置是根据传递给散列方法的键的hashCode()的结果来选择的。 这是为什么? 好吧,如果您请求某个键的值,则可以简单地重新计算数组中用于查找键及其关联值的索引,以便再次在数组中查找索引。 (需要更多的逻辑来处理映射到同一索引的键,但我只是想让你理解基本机制)然后使用equals()来validation计算索引处的键是否确实是请求密钥。

  1. 从中可以更清楚地了解为什么不可变键比可变键更好。 不可变键将始终保持相同的hashCode()值,并且哈希函数将再次找到正确的桶(= hashMap的数组中的索引)。

    这并不意味着可变密钥无法工作。 如果密钥上的突变不影响哈希代码,或者只要使用hashMap,密钥就不会发生变异,那么可变密钥将起作用。

  2. 一个不可变的密钥如何改变? 好吧,密钥本身可能无法更改,但键值映射可能会在业务逻辑中发生变化。 如果您使用地址作为键创建地图,则依赖于商店地址不会更改的事实。 如果商店的地址发生变化,您将无法使用新地址作为密钥在地图中找到它。 你的经理有一个有效的观点。

  3. 在Map中查找键的速度很大程度上取决于计算hashCode的速度。 对于String,此计算将循环遍历String中的所有字符。 如果你使用长字符串作为键并且有很多Map访问权限,这可能会导致性能瓶颈。 因此,Java的String实现会缓存哈希值,因此它只会被计算一次。 但是,如果再次使用相同的String实例(新实例将不具有缓存值),则只会避免计算哈希代码。 你可以intern()你使用的键,但只有当它可以显示确实存在性能瓶颈时才考虑这个,因为String interning确实带有它自己的开销。

  4. 如1中所述:如果哈希码不受突变影响,则可变密钥可以工作。 例如,使用Customer作为密钥,其中hashCode()仅基于客户的名称,那么只允许名称更改但允许其他值更改的Customer实现是可靠的密钥。

  1. 如果修改用作键的可变对象,则可能存在问题。 即使key存在, map.containsKey(modifiedKey)也可能返回false ,你必须迭代键才能找到它。 因此,尝试使用不可变或不修改mutable,因为它是一个键。

  2. 不可变对象永远不会改变。 有些方法看起来像是在改变对象,而是创建了一个新的副本。 例:

    字符串a =“A”;

    字符串b = a.substring(0); // substring创建了一个没有被修改的“A”的副本。

    a = a + b; // a + b创建一个新的字符串“AA”,不修改以前的字符串。

  3. 这可能有助于缓存-shes-in-java-collections这也是很好的为什么是不可变的对象在哈希映射中如此有效

  4. String已经实现了equalshashcode ,除非你绝对确定需要它,否则不需要发明另一个类来代替它。

    如第1点所述,您可以这样做,但您必须小心,不要修改您的可变对象。 但这不是一个很好的做法。

  1. 不可变的键不能改变。 因此,在插入时计算的哈希码不能改变。 因此,当您尝试从映射中获取元素时,要获取的对象的哈希码将根据已知的哈希码计算。 如果您的密钥从外部更改(它是可变的),则新密钥的哈希码将与您插入的哈希码不同。

  2. 我们来看一个例子吧。 ( 24

     public class RandomPair { int p; int q; public RandomPair(int p, int q) { this.p = p; this.q = q; } @Override public int hashCode() { return 31 * p + q; } @Override public boolean equals(Object obj) { if (!(obj instanceof RandomPair)) { return false; } if (obj == this) { return true; } RandomPair other = (RandomPair) obj; if (p != other.p) return false; if (q != other.q) return false; return true; } public static void main(String[] args) { RandomPair pair = new RandomPair(10, 10); Map map = new HashMap(); map.put(pair, 1); System.out.println(map.get(pair)); //returns 1 //someone somewhere just changed the value of pair pair.p = 20; //the object was the same, someone somewhere just changed value of pair and now you can't //find it in the map System.out.println(map.get(pair)); //had you made p and q final, this sort of modification wouldn't be possible //Strings are immutable and thus prevent this modification } } 
  3. 由于字符串是不可变的,因此一旦计算出的哈希码值可以再次重用。 哈希码是懒惰计算的。 即在第一次调用hashcode时,然后缓存hashcode的值。

通常,哈希映射中的键应该是不可变的。

看到这个

注意:如果将可变对象用作映射键,则必须非常小心。 如果在对象是地图中的键的同时以影响等于比较的方式更改对象的值,则不指定映射的行为。

您的密钥的哈希值在插入期间计算一次,哈希映射存储它,并且一旦修改了密钥,它就不会自动更新。 这就是为什么假设密钥是不可变的。

您的选择是:1。不要使用可变对象作为键。 尝试找到另一个键,或使用以前的键对象的不可变部分2.在将可变对象用作键时不要更改它们

  1. 可变键或对象意味着您可以修改对象[通过修改我的意思是您可以更改由对象表示的值]。 如果用equals和hashcode编写的逻辑使用这些可修改的值,这将影响其在HashMap存储。

  2. 理想的不变性意味着一旦初始化的对象在此之后就不能改变。 但是如果我们用HashMap专门讨论那么在equals和hashcode方法中使用的所有变量,如果它们可以被修改,那么该对象不应该被用作键,否则它可以用作键[但仍然不推荐]。

  3. 它不只是关于String ,任何关于会缓存其哈希码。 对于几乎所有对象,一次又一次地生成Hashcode [有一个原因,我说几乎就像在某些情况下它可以改变]。 Hashcode缓存在Object头中。

  4. 如果你想使用可变对象作为密钥,那么你应该去IdentityHashMap 。 只是阅读它们,它们在这种情况下很有用。