字符串与字符串缓冲区作为HashMap键的区别

5

我试图理解为什么在使用作为HashMap键时,String和Stringbuilder/StringBuffer的处理方式是不同的。让我通过以下示例说明我的困惑:

示例1,使用String:

String s1 = new String("abc");
String s2 = new String("abc");
HashMap hm = new HashMap();
hm.put(s1, 1);
hm.put(s2, 2);
System.out.println(hm.size());

上面的代码段会输出“1”。
例2,使用StringBuilder(或StringBuffer):
StringBuilder sb1 = new StringBuilder("abc");
StringBuilder sb2 = new StringBuilder("abc");
HashMap hm = new HashMap();
hm.put(sb1, 1);
hm.put(sb2, 2);
System.out.println(hm.size());

上面的代码片段打印出'2'。
有人可以解释一下为什么行为不同吗?

7
请注意,sb1.equals(sb2) 的返回值为 false。 - user395760
2
因为它们是完全不同的东西。字符串就是字符串。直到你将其转换为字符串之前,字符串构建器并不是一个字符串。 - Dave Newton
你能解释一下为什么你认为它们应该是相同的吗? - Peter Lawrey
这个不一致性的确切差异是什么?它们都是内部char[]。难道不是吗? - ambar
字符串是不可变的,这意味着它不会改变。StringBuilder是可变的,这意味着即使两个StringBuilder现在包含相同的文本,也不意味着它们将来会这样。 - Peter Lawrey
4个回答

4

2

StringBuilder使用Object的默认hashcode()实现,而字符串按值进行映射键比较。

Map的工作方式(特别是HashMap)是利用对象的hashcode,而不是类的内容。

请注意你的地图缺乏参数化:

HashMap yourMap = new HashMap();
//Should be
Map<String, Integer> yourMap = new HashMap<>();

没有理由创建新的字符串对象而不是分配已经存在的字面量:

String s1 = "abc";

0

我注意到你正在使用new String("abc"); 这意味着你知道String a = "abc"和String b = "abc"是相同的。

所以,a == b返回true。而a.equals(b)也返回true。

然而,对于StringBuffers来说,情况并非如此,因为它的equals方法不考虑对象的值,只考虑其哈希码。

如果你查看StringBuffer,你会发现它使用的是Object.equals方法,即

public boolean equals(Object obj) {
    return (this == obj);
}

字符串的等于是:

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String) anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                        return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

1
所以,a == b 返回true... 不,它不会,因为那些字符串是不同的对象,因为它们是使用 new 创建而不是内部化。 - GriffeyDog
尝试一下:String a = "abc"; String b = "abc"; System.out.println(a == b); - Alexandre Santos
你应该自己尝试一下。代码是String s1 = new String("abc");,而不是String a = "abc";。请注意使用了new关键字。 - GriffeyDog
哦,伙计,看看我发布的代码。我没有使用OP提供的那个。请阅读我在帖子开头所说的话。 - Alexandre Santos
我现在明白了,虽然我不确定它如何适用于OP的问题,但归根结底是因为StringBuilder没有覆盖equalshashCode。无论如何,对于我的误读表示抱歉。 - GriffeyDog
没问题,我以前也做过同样的事情;现在,关于代码是否适用于OP的问题,只有他/她自己能决定。如果你想知道,你可以问他/她。 - Alexandre Santos

0
为了理解其行为,了解哈希映射的工作原理非常重要。哈希映射使用由键对象(在本例中为字符串对象)返回的哈希码值将它们存储到适当的内存区块中,称为桶。因此,当检索与其关联的值时,哈希映射需要定位存储键的区块,然后返回该键对应的值。哈希映射从键的哈希码值识别区块。
现在,想象一下,如果我有两个具有相同哈希码值的对象,会发生什么?在这种情况下,哈希映射需要首先知道这两个对象是否相同,因为如果它们是相同的,那么它意味着地图中只有一个条目,并且与该键关联的现有值将被替换为新值。但是,仅仅拥有相同的哈希码并不意味着两个键相等。因此,通过调用键对象上的.equals()方法来确定相等性。如果.equals()返回true,则对象相等,在这种情况下,哈希映射需要更新现有条目的值。但是如果.equals()返回false呢?在这种情况下,两个对象是不同的,应该作为单独的条目存储。因此,它们并排存放在同一个隔间中。因此,在检索值时,使用输入键的哈希码来到达存储它的隔间,然后如果隔间包含多个对象,则检查输入键是否与隔间中的每个对象相等,如果匹配,则返回相关联的值。
现在,让我们将上述理论应用到您的代码中。如果两个String对象的内容相等,则它们是相等的。根据规则,如果两个对象相等,则它们应该返回相同的哈希码。但请记住,反之不成立。如果两个对象返回相同的哈希码,并不意味着它们相等。这一开始可能会令人困惑,但您可以通过几次迭代来克服它。即使两个具有相同内容的字符串是物理上不同的对象,它们也是相等的并且返回相同的哈希码,因此当用作哈希映射中的键时,它们总是映射到相同的条目。这就是行为。
String类重写了默认的equals()方法,该方法依赖于内容相等性而不是引用相等性来判断两个对象是否相等。它可以这样做是因为字符串是不可变的。但是,StringBuffer并没有这样做。它仍然依赖于引用相等性。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接