一個有趣的問題: 如何用HashSet來儲存重複的字串?

AlanKeene發表於2019-02-23

1. 前言

今天,我們來探討一個實際中不常用但卻比較有意思的問題。它能幫助你理解 “HashSet中的鍵值是唯一的,不可重複的” 這句話的真正含義,也考驗你對問題的思考深度。

注:實際應用中,我們一般是用 ArrayList 集合來儲存相同的字串的,不會用 HashSet 來存。

我們平時都看到或聽說 HashSet 是不能用來存放重複的字串的,是真的存放不了嗎?如果面試問你這個問題,你能給出解決方案嗎?

2. 參考解答

先給出參考解答,然後我們再來分析為什麼。

解答: 雖然我們不能用 HashSet 來存放 String 型別重複的字串,但我們可以用 HashSet 來儲存 StringBuilder 型別重複的字串呀。

public class HashSetTest {
    public static void main(String[] args){

        // 用 HashSet 來存放 String 型別的重複的字串會發生什麼?

        HashSet<String> hs1 = new HashSet<>();
        String s1 = new String("aaa");
        String s2 = new String("aaa");
        String s3 = new String("aaa");
        hs1.add(s1);
        hs1.add(s2);
        hs1.add(s3);
        System.out.println("hs1:"+hs1); // 重複的字串是存不進去的

        // 用 HashSet 來存放 StringBuilder 型別的重複的字串又會發生什麼?

        HashSet<StringBuilder> hs2 = new HashSet<>();
        StringBuilder sb1 = new StringBuilder("aaa");
        StringBuilder sb2 = new StringBuilder("aaa");
        StringBuilder sb3 = new StringBuilder("aaa");
        hs2.add(sb1);
        hs2.add(sb2);
        hs2.add(sb3);
        System.out.println("hs2:"+hs2); // 咦,結果發現重複的字串也能存進去了


        // 那為什麼呢?我們來列印一個各個物件的hashCode看一下

        System.out.println("s1的hashCode:"+s1.hashCode());
        System.out.println("s2的hashCode:"+s2.hashCode());
        System.out.println("s3的hashCode:"+s3.hashCode());
        System.out.println("sb1的hashCode:"+sb1.hashCode());
        System.out.println("sb2的hashCode:"+sb2.hashCode());
        System.out.println("sb3的hashCode:"+sb3.hashCode());

    }
}
複製程式碼

輸出結果:

hs1:[aaa]
hs2:[aaa, aaa, aaa]
s1的hashCode:96321
s2的hashCode:96321
s3的hashCode:96321
sb1的hashCode:356573597
sb2的hashCode:1735600054
sb3的hashCode:21685669
複製程式碼

從列印結果來看,我們是不能用 HashSet 來存放 String 型別的重複字串的(如hs1),但我們是可以用HashSet來存放 StringBuilder 型別的重複字串。

3. 為什麼?

從列印的 hashCode 來看,String 型別,相同字串的不同 String 物件雜湊值是一樣的。而對於 StringBuilder 型別,相同字串的不同物件雜湊值是不同的。

要知道這個問題的答案,我們首先得了解 JDK 是如何判斷兩個物件是否相同的。

那 JDK 是如何判斷兩個物件是否相同的呢?

參考解答: JDK 會先判斷兩個物件的 hashCode 是否相同,如果 hashCode 不同,則說明肯定是兩個不同的物件了;如果 hashCode 相同再通過 equals() 方法進行進一步比較,如果 equals 方法返回 true,則說明兩個物件是相同的,如果equals方法返回 false 說明兩個物件不同。

具體驗證思路如果你感興趣,請檢視: JDK 是如何判斷兩個物件是否相同的?判斷的流程是什麼?

那為什麼相同字串的不同 String 物件雜湊值是一樣的,而且還被 JDK 判斷為相同的物件了呢?

因為 String 類複寫了 Object 類的 hashCode() 和 equals() 方法,並實現了自己的 hashCode 值生成演算法和 equals 的比較規則,具有相同字串內容的不同 String 物件在初始化時生成的 hashCode 值是一樣的,並且 String 類 equals() 方法比較的是兩個字串的內容,而不是記憶體地址值,這兩個條件同時成立, 這就使 JDK 把具有相同內容的不同 String 物件判斷為相同的物件了,就不會存入 HashSet 集合中。

而 StringBuilder 為什麼就可以呢?它相同內容的不同物件的雜湊值值為什麼是不同的?

檢視 StringBuilder 類的原始碼你會發現,因為 StringBuilder 並沒有複寫 Object 類的 hashCode() 方法和 equals() 方法,StringBuilder 用的是父類 Object 類的 hashCode 生成演算法,也就是用 native 層的 hashCode 生成演算法,很大概率產生的雜湊值是不一樣的,即使產生了一樣的雜湊值,Object 類的 equals() 方法比較的是兩個物件的記憶體地址,而不是兩個物件的內容,這就使 JDK 把具有相同內容的 StringBuilder 物件判斷為不同的物件,就可以存入 HashSet 集合中了。

相關文章