java集合HashMap、HashTable、HashSet詳解


一、Set和Map關系

Set代表集合元素無序,集合元素不可重復的集合,Map代表一種由多個key-value組成的集合,map集合是set集合的擴展只是名稱不同,對應如下

二、HashMap的工作原理

        HashMap基於 hashing原理,通過put()和get()方法儲存和獲取對象。
        put()方法: 它調用鍵對象的hashCode()方法來計算hashcode值,系統根據hashcode值決定該元素在bucket位置。如果兩個對象key的hashcode返回值相同,那他們的存儲位置相同,如果這兩個Entry的key通過equals比較返回true,新添加Entry的value將覆蓋集合中原有Entry的value,但key不會覆蓋;如果這兩個Entry的key通過equals比較返回false,新添加的Entry將與集合中原有Entry形成Entry鏈,而且新添加Entry位於Entry鏈的頭部。put源碼如下: 
public V put(K paramK, V paramV) {
        //如果key為空,調用putForNullKey方法
        if (paramK == null)
            return putForNullKey(paramV);
        //根據key的keyCode計算Hash值
        int i = hash(paramK.hashCode());
        //搜索指定hash值的對應在table中的索引
        int j = indexFor(i, this.table.length);
        //如果j索引處的Entry不為空,通過循環遍歷localEntry元素的下一個元素
        for (Entry localEntry = this.table[j]; localEntry != null; localEntry = localEntry.next) {
            Object localObject1;
            //找到指定key與放入key相等(hash值相同,通過equals比較返回true)
            if ((localEntry.hash == i)
                    && ((((localObject1 = localEntry.key) == paramK) || (paramK
                            .equals(localObject1))))) {
                Object localObject2 = localEntry.value;
                localEntry.value = paramV;
                localEntry.recordAccess(this);
                return localObject2;
            }
        }
        //如果j索引Entry為null,此處沒有Entry
        this.modCount += 1;
        //將key、value添加到i索引處
        addEntry(i, paramK, paramV, j);
        return null;
    }

    void addEntry(int paramInt1, K paramK, V paramV, int paramInt2) {
        //獲取指定bucketIndex索引處Entry
        Entry localEntry = this.table[paramInt2];
        //將新創建的Entry放入bucketIndex索引處,並讓新的Entry指向原來的Entry
        this.table[paramInt2] = new Entry(paramInt1, paramK, paramV, localEntry);
        //如果map中的key-value數量超過
        if (this.size++ >= this.threshold)
        //table對象的長度擴充到2倍
            resize(2 * this.table.length);
    }

put方法三種情況,如圖:

 

get()方法:當HashMap的每個bucket里存儲的Entry只是單個Entry,即沒有通過指針產生Entry鏈時,此時HashMap具有最好的性能。當程序通過key取出對應value時,系統先計算出該key的hashCode()返回值,再根據該hashCode返回值找出該key在table數組中的索引,然后取出該索引處的Entry,最后返回該key對應的value值。get源碼如下:

public V get(Object paramObject) {
       //如果key為空,調用getForNullKey取出對應的value
        if (paramObject == null)
            return getForNullKey();
        //根據key的hashCode值計算hash碼
        int i = hash(paramObject.hashCode());
        //直接取出table數組中指定索引處的值
        Entry localEntry = this.table[indexFor(i, this.table.length)];
        while (localEntry != null) {
            Object localObject;
            //如果該Entry的key與被搜索key相同
            if ((localEntry.hash == i)
                    && ((((localObject = localEntry.key) == paramObject) || (paramObject
                            .equals(localObject)))))
                return localEntry.value;
            //搜索該Entry鏈的下一個
            localEntry = localEntry.next;
        }

        return null;
    }
 從代碼看出,HashMap的每個bucket里只有一個Entry,HashMap可以根據索引快速取出該bucket里的Entry。
在發生Hash沖突的情況下,單個bucket里存儲的不是一個Entry,而是一個Entry鏈,系統只能按順序遍歷每個Entry,直到找到想搜索的Entry。

HashMap有兩個參數影響其性能:

1. 初始容量和加載因子。默認初始容量是16,加載因子是0.75。容量是哈希表中桶(Entry數組)的數量,初始容量只是哈希表在創建時的容量。加載因子是哈希表在其容量自動增加之前可以達到多滿的一種尺度。當哈希表中的條目數超出了加載因子與當前容量的乘積時,通過調用rehash 方法將容量翻倍。

2. 加載因子過高雖然減少了空間開銷,但同時也增加了查詢成本(加載因子是表示Hsah表中元素的填滿的程度.若:加載因子越大,填滿的元素越多,好處是,空間利用率高了,但:沖突的機會加大了.反之,加載因子越小,填滿的元素越少,好處是:沖突的機會減小了,但:空間浪費多了)。在設置初始容量時應該考慮到映射中所需的條目數及其加載因子,以便最大限度地降低rehash 操作次數。如果初始容量大於最大條目數除以加載因子(實際上就是最大條目數小於初始容量*加載因子),則不會發生 rehash 操作。

3.HashMap存放的元素越來越多,到達臨界值(閥值)threshold時,就要對Entry數組擴容,這是Java集合類框架最大的魅力,HashMap在擴容時,新數組的容量將是原來的2倍,由於容量發生變化,原有的每個元素需要重新計算bucketIndex,再存放到新數組中去,也就是所謂的rehash。HashMap默認初始容量16,加載因子0.75,也就是說最多能放16*0.75=12個元素,當put第13個時,HashMap將發生rehash,rehash的一系列處理比較影響性能,所以當我們需要向HashMap存放較多元素時,最好指定合適的初始容量和加載因子,否則HashMap默認只能存12個元素,將會發生多次rehash操作。

三、HashMap和Hashtable的區別

HashMap和Hashtable都實現了Map接口,主要的區別有:線程安全性,同步(synchronization),以及速度。HashMap幾乎可以等價於Hashtable,除了HashMap是非synchronized的,並可以接受null(HashMap可以接受為null的鍵值(key)和值(value),而Hashtable則不行)。

HashMap是非synchronized,而Hashtable是synchronized,意味着Hashtable是線程安全的,多個線程可以共享一個Hashtable;而多個線程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的擴展性更好。另一個區別是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器。 HashMap可以通過下面的語句進行同步:Map m = Collections.synchronizeMap(hashMap);
 
四、HashMap和HashSet的區別
HashSet實現了Set接口,它不允許集合中有重復的值,HashMap實現了Map接口,Map接口對鍵值對進行映射。
HashSet擴展了HashMap,所以底層還是用到map存儲,存儲實現同map一致,HashMap儲存鍵值,HashSet存儲對象。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM