String Vs Stringbuffer 作为 HashMap 键

Posted

技术标签:

【中文标题】String Vs Stringbuffer 作为 HashMap 键【英文标题】:String Vs Stringbuffer as HashMap key 【发布时间】:2014-07-10 12:32:45 【问题描述】:

我试图理解为什么 String 和 Stringbuilder/StringBuffer 在用作 Hashmap 键时会被区别对待。让我通过以下插图更清楚地说明我的困惑:

示例 #1,使用字符串:

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());

上面的代码 sn-p 打印 '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());

上面的代码 sn -p 打印 '2'。

谁能解释一下为什么会出现这种行为差异。

【问题讨论】:

请注意sb1.equals(sb2) 是假的。 因为它们是完全不同的东西。字符串是字符串。字符串生成器在将其转换为字符串之前不是字符串。 你能解释一下为什么你认为它们应该是一样的吗? 造成这种不一致的确切区别是什么。两者都是内部 char[]。不是吗? 字符串是不可变的,这意味着它不会改变。 StringBuilder 是可变的,这意味着仅仅因为两个 StringBuilder 现在恰好包含相同的文本,并不意味着它们将来会。 【参考方案1】:

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

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

请注意,您的地图缺少参数化:

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

并且没有理由创建新的字符串对象而不是分配内部文字:

String s1 = "abc";

【讨论】:

【参考方案2】:

StringBuilder/Buffer 不会覆盖 hashCode 和 equals。这意味着对象的每个实例都应该是唯一的哈希码,并且它的值或状态无关紧要。您应该使用字符串作为键。

StringBuilder/Buffer 也是可变的,这通常不适合用作 HashMap 的键,因为将值存储在其下可能会导致修改后无法访问该值。

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/StringBuilder.java

【讨论】:

【参考方案3】:

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

所以,a == b 返回真。并且 a.equals(b) 返回 true。

但是同样的事情对 StringBuffers 不起作用,因为它的 equals 不考虑对象的值,只考虑它的哈希码。

如果您查看 StringBuffer,您会发现它使用 Object.equals,即

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

虽然 String 的等号是:

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;

【讨论】:

所以,a == b 返回 true... 不,这些字符串是不同的对象,因为它们是用 new 创建的,而不是实习生的。 自己试试吧:String a = "abc"; String b = "abc"; System.out.println(a == b); 您应该自己尝试一下。代码是String s1 = new String("abc");,而不是String a = "abc";。注意new的使用。 哦,伙计,请阅读我发布的代码。我没有使用OP提供的那个。阅读我在帖子开头所说的内容。 我现在看到了,虽然我不确定这如何适用于 OPs 问题,归结为 StringBuilder 没有覆盖 equalshashCode。无论如何,对于误读,我们深表歉意。【参考方案4】:

要了解行为,了解 Hashmap 的工作原理很重要。哈希映射使用键对象(在这种情况下为字符串对象)返回的哈希码值将它们存储到称为存储桶的适当内存隔间中。因此,当检索与其关联的值时,hashmaps 需要找到存储密钥的隔间,然后针对该密钥返回值。散列图从键的散列码值识别隔间。

现在,想象一下,如果我有两个具有相同哈希码值的对象,会发生什么?在这种情况下,hashmap 需要首先知道两个对象是否相同,因为如果它们相同,则意味着映射中只有一个条目,并且与该键关联的现有值将被新值替换。但是,仅仅具有相同的哈希码并不意味着两个键是相等的。因此,相等性是通过在关键对象上调用 .equals() 方法来确定的。如果 .equals() 返回 true,则对象相等,在这种情况下,哈希映射需要更新现有条目的值。但是如果 .equals() 返回 false 怎么办?在这种情况下,两个对象是不同的,应该存储为单独的条目。因此,它们并排存放在同一个隔间中。因此,在检索值时,输入键的哈希码用于到达存储它的隔间,然后如果隔间包含多个对象,则检查输入键是否与隔间中的每个对象相等,如果匹配则返回关联的值。

现在,让我们将上述理论应用于您拥有的代码。如果字符串对象的内容相等,则它们是相等的。按照规则,如果两个对象相等,它们应该返回相同的哈希码。但请记住,converse 不是真的。如果两个对象返回不需要它们相等的相同哈希码。起初这似乎令人困惑,但您可以在几次迭代中克服它。具有相同内容的两个字符串相等并返回相同的哈希码,即使它们在物理上是不同的对象,因此当用作 hashmap 中的键时,将始终映射到相同的条目。因此行为。

String 类覆盖默认的 equals() 方法,该方法表示如果两个对象具有相同的引用,则它们与依赖于内容相等的对象具有相同的引用。它可以这样做,因为字符串是不可变的。但是,StringBuffer 不这样做。它仍然依赖于引用相等性。

【讨论】:

以上是关于String Vs Stringbuffer 作为 HashMap 键的主要内容,如果未能解决你的问题,请参考以下文章

java(String和StringBuffer分别作为参数传递)

StringBuffer和String,StringBuilder的区别?

StringBuffer作为参数传递的问题

StringBuffer类

Java回顾-String/StringBuilder/StringBuffer

String 和StringBufferStringBuilder的区别?