1. 程式人生 > >java HashSet原始碼簡單剖析

java HashSet原始碼簡單剖析

1. 首先明確hash演算法:
既然都是HashSet集合了,肯定與hash演算法有關,我的理解就像是在查詢新華字典(雜湊表)一樣,按照拼音(雜湊值)先找到在哪頁(哪個儲存區域),再在該頁(區域)查詢。比全部遍歷提高了查詢效率。

2. HashSet集合是如何保證唯一性的?
通過追溯add()方法,瞭解到底層為HashMap的put(K key, V value)方法,原始碼如下:

 public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if
(key == null) return putForNullKey(value); int hash = hash(key); //與物件的hashCode()有關 int i = indexFor(hash, table.length);//查詢hash對應的儲存區域位置 //迴圈倒序比較(先進入的最後比較)該區域的值 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if
(e.hash == hash && ((k = e.key) == key || key.equals(k))) { //劃重點! V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue;//新增失敗 } } modCount++; addEntry(hash, key, value
, i);//按雜湊值新增成功 return null; }

很明顯,元素唯一性和儲存效率取決於物件的hashCode()和equals()方法。

3. 為什麼要重寫所裝入物件的hashCode()和equals()方法?
如果不重寫就會用繼承自Object類的方法,而Object.hashCode()代表物件的十進位制記憶體地址值,造成雜湊值肯定不同,都不用equals()直接導致例如兩個同名的學生物件能同時存在,無法滿足“定製的”唯一性。
但是如果重寫成如下方式了?

    public int hashCode() {
        return 0;
    }

把雜湊值變成一樣的(0),於是都能在一個區域內用equals()比較物件的成員變數是否相同,如果不同則新增到集合。看起來不錯,但是就變成了遍歷物件了,效率太低。於是我們需要定製規則讓不同的物件雜湊值不同,於是與物件的成員變數關聯起來,但是簡單的成員變數的雜湊值相加的和可能會造成一樣的雜湊值,這就造成了衝突。為了儘可能區分,於是乘以一個數,如下系統生成的hashCode():(為什麼是31?http://blog.csdn.net/tayanxunhua/article/details/20525251

    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

這樣,通過呼叫物件hashCode(),相同雜湊值的就會放在一起,然後用重寫的equals()判斷是否成員變數一致;不同的就直接加到集合中。

更進一步,我們或許可以得出這樣的結論(其實是規定):

  • 如果兩個物件相同(equals方法返回true),那麼它們的hashCode值一定要相同;
  • 如果兩個物件的hashCode相同,它們並不一定相同。

當然,你未必要按照要求去做,但是如果你違背了上述原則就會發現在使用容器時,相同的物件可以出現在Set集合中,同時增加新元素的效率會大大下降(對於使用雜湊儲存的系統,如果雜湊碼頻繁的衝突將會造成存取效能急劇下降)。