字符串作为哈希图 [duplicate] 中的键

String as key in hashmap [duplicate]

提问人:rickygrimes 提问时间:8/3/2013 最后编辑:rickygrimes 更新时间:3/20/2014 访问量:27973

问:

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

  1. 哈希图的可变键和不可变键之间的区别。
  2. 如果使用可以更改的不可变密钥,会发生什么情况?- 我知道这没有意义,但我想清楚地了解我的经理在这里说什么。
  3. 一些帖子谈到字符串,如果用作哈希映射中的键,缓存它们的哈希码 - 这意味着什么?
  4. 如果假设我在哈希图中使用可变对象作为键,实现了哈希码和等号,那么它会起作用吗?我假设它会,因为如果键更改,contains 方法将查看键是否存在。如果它不存在,它将放置该条目,以便您将来可以获取它。

如果之前已经讨论过,我并不是要创建重复的帖子。如果我错过了阅读包含我所有问题答案的帖子,请指出我。如果没有,请用通俗易懂的语言解释我的上述问题,以便将来对其他读者:)有用。随意编辑我帖子的主题,以便将来如果有人有类似的问题,他们会直接登陆这里:)

爪哇岛

评论

1赞 Spencer 8/3/2013
这应该可以回答您的问题 stackoverflow.com/questions/214714/mutable-vs-immutable-objects
0赞 s.d 8/3/2013
不断变化的哈希码破坏了 HashMap 的契约。在这种情况下,您可能希望按对象引用进行映射。或者将映射转换为对象属性关系。

答:

0赞 Vadim 8/3/2013 #1

一般来说,哈希图中的键应该是不可变的。

看到这个

注意:如果将可变对象用作映射,则必须格外小心 钥匙。如果对象的值 以影响等于比较的方式进行更改,而 对象是映射中的键。

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

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

评论

0赞 rickygrimes 8/3/2013
你能回答我上面问的每一个问题吗?这真的会有所帮助。
2赞 Tala 8/3/2013 #2
  1. 如果修改用作键的可变对象,则可能会出现问题。 即使密钥存在,也可能会返回,您必须遍历密钥才能找到它。 因此,请尝试使用 immutable,或者不要在它是键时修改 mutable。map.containsKey(modifiedKey)false

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

    字符串 a = “A”;

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

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

  3. 这可能有助于在java集合中缓存哈希,这也很好,为什么在哈希图中如此有效

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

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

评论

0赞 rickygrimes 8/3/2013
非常感谢您的链接 - stackoverflow.com/questions/10342859/...。它回答了我所有的问题。
0赞 rickygrimes 8/3/2013
我当然可以。但老实说,我仍然不太清楚第 #2 点。另外,请修改您对我的观点 #4 的回答。我指的是不可变的对象。
1赞 bsd 8/3/2013 #3
  1. 不可变键无法更改。因此,在插入时计算的哈希码不能更改。因此,当您尝试从映射中获取元素时,将根据已知的哈希码计算要获取的对象的哈希码。如果您的密钥从外部更改(它是可变的),则新密钥的哈希码将与您插入的哈希码不同。

  2. 让我们看一个例子。for( 和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<RandomPair, Integer> map = new HashMap<RandomPair, Integer>();
    
            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 的值。

评论

0赞 rickygrimes 8/3/2013
谢谢 bsd。您能否补充几句关于我的经理在说什么?我们从客户那里接收 JSON 字符串的数据。所以他们肯定会改变地址。我想知道这会如何影响我的逻辑?
0赞 bsd 8/3/2013
不要将地址保留为计算的一部分。作为常量的字段,可能是其唯一的 10 位标识号(可能是 SSN)应该是 的一部分。姓名可能会更改,地址可能会更改。如果可以找出对象中发生的变化,则可以从地图中移除该项目,然后使用新的新值重新插入。hashcodeequalshashcodePerson
29赞 bowmore 8/3/2013 #4

第一:HashMap是如何工作的?

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

  1. 由此应该更清楚为什么不可变键比可变键更好。不可变键将始终保持相同的值,哈希函数将再次找到正确的存储桶( = hashMap 数组中的索引)。hashCode()

    这并不意味着可变键不能工作。如果键上的突变不影响哈希码,或者只要使用 hashMap,键就不会发生突变,则可变键将起作用。

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

  3. 在 Map 中查找密钥的速度很大程度上取决于计算 hashCode 的速度。对于 String,此计算将循环 String 中的所有字符。如果使用长字符串作为键,并且具有大量映射访问权限,则可能会导致性能瓶颈。因此,Java 的 String 实现会缓存哈希值,因此它只会计算一次。但是,只有在再次使用同一实例时,才能避免计算哈希代码(新实例将没有缓存值)。您可以使用密钥,但只有在可以证明确实存在性能瓶颈时才考虑这一点,因为实习确实有其自身的开销。Stringintern()String

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

评论

0赞 rickygrimes 8/3/2013
非常好。谢谢bowmore。
0赞 assylias 8/3/2013
String 的哈希码已经缓存了......
0赞 Anton Belev 8/3/2013
我想象,如果您更改可变键,那么 HashMap 应该计算它是新的并重新定位它。我不知道 Java 中 HashMap 的实现是什么,但我认为检查键是否已更改是合理的,如果是 -> 计算新的 -> 重新定位元素并释放之前的位置。hashCode()hashCode()
6赞 bowmore 8/3/2013
@AntonBelev Map 实现(尤其是通用实现)如何能够知道密钥的哈希码何时更改?Map 可以在每次获取之前检查所有包含的键,但这只会破坏 Map 的目的:快速获取与键关联的值。
0赞 Lokesh 8/3/2013 #5
  1. 可变键或对象意味着您可以修改对象 [通过修改,我的意思是您可以更改对象表示的值]。如果用 equals 编写的逻辑和哈希码使用这些可修改值,这将影响其存储。HashMap

  2. 理想情况下,不可变性意味着对象一旦初始化就不能在此之后更改。但是,如果我们具体地谈论在等于和哈希码方法中使用的所有变量,如果它们可以被修改,那么该对象不应该用作键,否则它可以用作键[但仍然不推荐]。HashMap

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

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