原文: http://www.itkeyword.com/doc/1200285227501001789/string-vs-stringbuffer-as-hashmap-key
以下内容为对该文章的理解性翻译,部分认为无用的内容被删除
我试图理解为什么当用作HashMap密钥时,String和StringBuilder / StringBuffer被区别对待。让我通过以下内容使我的困惑更加清楚:
例1:使用String:
String s1 = new String("abc");
String s2 = new String("abc");
HashMap<String,Object> 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<StringBuilder,Object> hm = new HashMap<>();
hm.put(sb1, 1);
hm.put(sb2, 2);
System.out.println(hm.size());
- 上面的代码片段显示为“ 2”;
接下来是一些讨论(只翻译了其中的部分):
-
问:造成这种不一致的确切区别是什么。两者都是内部char []。不是吗?
-
答:
-
String是不可变的。StringBuilder是可变的,这意味着仅因为两个StringBuilder碰巧现在包含相同的文本,并不意味着它们将来会包含相同的文本。
-
理解:测试以下代码
String s1 = new String("abc"); String s2 = new String("abc"); System.out.println(s1.hashCode()); System.out.println(s2.hashCode()); System.out.println("========================================"); StringBuilder sb1 = new StringBuilder("abc"); StringBuilder sb2 = new StringBuilder("abc"); System.out.println(sb1.hashCode()); System.out.println(sb2.hashCode());
-
StringBuilder / Buffer没有重写hashCode()和equals()函数。这意味着该对象的每个实例都应该是唯一的哈希码,并且其值或状态无关紧要。
-
你应该使用String作为Key。StringBuilder / Buffer是可变的,将其用作HashMap的键通常不是一个好主意,因为在其下存储值可能会修改,导致以后无法访问原始值。
-
StringBuilder
使用Object
默认的hashCode()
函数实现,而String则通过比较字符串中的字符进行判断是否相等。Map的工作方式(特别是HashMap)是利用对象的hashcode
,而不是类的内容(理解:其中保存的内容);
-
-
查看源码:
- 如果查看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; }
-
接下来是一些争论:争论的中心点在于String的intern()那个技术点;
- 你可以参考我的文章: https://blog.csdn.net/Hubz131/article/details/105158957
- 如果我的文章不能是你满意,也可以自行搜索;
-
现在,想象一下,如果我有两个具有相同哈希码值的对象,将会发生什么?在这种情况下,HashMap首先需要知道两个对象是否相同,因为如果它们相同,则意味着映射中只有一个条目,并且与该键关联的现有值将被新值替换。但是,仅具有相同的哈希码并不意味着两个密钥都相等。因此,是否相等是通过在关键对象上调用
equals()
方法来确定的。如果equals()
返回true,则对象相等。在这种情况下,哈希映射需要更新现有条目的值。但是如果equals()
返回false怎么办?在这种情况下,两个对象都不相同,应将其存储为单独的条目。因此,它们都将存储在HashMap中。 在检索值时,将使用输入值的哈希码到达存储它的隔离专区(即:位桶数组的相应位置),然后,如果隔离专区包含多个对象(由于使用拉链法解决哈希碰撞的原因),则将检查输入键是否与隔离专区中的某个对象相等,如果匹配,则返回关联的值;-
仅具有相同的哈希码并不意味着两个密钥都相等:
-
String s1 = new String("uP"); String s2 = new String("v1"); System.out.println(s1.hashCode()); System.out.println(s2.hashCode()); HashMap<String,Object> hm = new HashMap<>(); hm.put(s1, 1); hm.put(s2, 2); System.out.println(hm.size());
-
-
现在,让我们将上述理论应用于您拥有的代码。如果字符串对象的内容相等,则它们相等。并且根据规则,如果两个对象相等,则它们应返回相同的哈希码。但是请记住,相反是不正确的。如果两个对象返回相同的哈希码,则内容不一定相等。具有相同内容的两个字符串相等,即使它们在物理上是不同的对象,它们也会返回相同的哈希码,因此在HashMap中用作键时,它们始终会映射到同一条目。因此,就会出现以上现象(指的是例1)。
-
String类会覆盖默认的equals()方法,该方法表示两个对象如果具有相同的引用,而这些引用依赖于内容的相等性,则它们相等。之所以可以这样做是因为String是不可变的。但是,StringBuffer不会这样做。它仍然依赖于引用相等(地址)。
总结:
三者相比,String更适合作为HashMap的键
1、StringBuffer/Builder作为键的话,如果后期内容发生了更改,就无法使用历史插入的那个值进行查询;
2、StringBuffer/Builder如果出现hashCode一样的话,不会对内容进行比较,直接回替换另一个相同hashCode的值,违背了正常思维(我是这么理解的,希望指正)