Java集合源碼分析--HashMap

來源:互聯網
上載者:User

標籤:還需要   繼承   htable   ati   元素   api   record   抽象   []   

轉載自 http://www.cnblogs.com/zhangyinhua/p/7698642.html#_label0一,關於HashMap API定義
1、雜湊表基於map介面的實現,這個實現提供了map所有的操作,並且提供了key和value可以為null,(HashMap和HashTable大致上是一樣的除了hashmap是非同步和允許key和value為null),
這個類不確定map中元素的位置,特別要提的是,這個類也不確定元素的位置隨著時間會不會保持不變。
2.
HashMap的執行個體有兩個參數影響效能,初始化容量(initialCapacity)和loadFactor載入因子。
二,HashMap 的屬性

 
HashMap的執行個體有兩個參數影響其效能。

  初始容量:雜湊表中桶的數量  載入因子:雜湊表在其容量自動增加之前可以達到多滿的一種尺度

  當雜湊表中條目數超出了當前容量*載入因子(其實就是HashMap的實際容量)時,則對該雜湊表進行rehash操作,將雜湊表擴充至兩倍的桶數。

//Java中預設初始容量為16,載入因子為0.75。        
HashMap hm=new HashMap(); for(int i=0;i<17;i++){ hm.put(i,i); } System.out.println(hm.size());//17 System.out.println(hm.table.length);//32

 1)loadFactor載入因子

    定義:loadFactor譯為裝載因子。裝載因子用來衡量HashMap滿的程度,是控制數組存放資料的疏密程度。計算HashMap的即時裝載因子的方法為:size/capacity,而不是佔用桶的數量去除以capacity。

    loadFactor越趨近於1,那麼數組中存放的資料(entry)也就越多,但我們在通過key拿到我們的value時,是先通過key的hashcode值,找到對應數組中的位置,

如果該位置中有很多元素,則需要通過equals來依次比較鏈表中的元素,拿到我們的value值,這樣花費的效能就很高,

    loadFactor越趨近於0,那麼數組中存放的資料也就越稀,分散的太開,浪費很多空間,這樣也不好,

所以在hashMap中loadFactor的初始值就是0.75,一般情況下不需要更改它。

  2)桶

    數組中每一個位置上都放有一個桶,每個桶裡就是裝一個鏈表,鏈表中可以有很多個元素(entry),這就是桶的意思。也就相當於把元素都放在桶中。

  3)capacity

    capacity譯為容量代表的數組的容量,也就是數組的長度,同時也是HashMap中桶的個數。預設值是16。

  4)size的含義

    size就是在該HashMap的執行個體中實際儲存的元素的個數

  5)threshold的作用

    threshold = capacity * loadFactor,當Size>=threshold的時候,那麼就要考慮對數組的擴增了,也就是說,這個的意思就是衡量數組是否需要擴增的一個標準。

    注意這裡說的是考慮,因為實際上要擴增數組,除了這個size>=threshold條件外,還需要另外一個條件。

    什麼時候會擴增數組的大小?在put一個元素時先size>=threshold並且還要在對應數組位置上有元素,這才能擴增數組。

二、HashMap的源碼分析2.1、HashMap的層次關係與繼承結構

 

  1)HashMap繼承結構

 

    

 

  上面就繼承了一個abstractMap,也就是用來減輕實現Map介面的編寫負擔。

  2)實現介面

    

 

    Map<K,V>:在AbstractMap抽象類別中已經實現過的介面,這裡又實現,實際上是多餘的。但每個集合都有這樣的錯誤,也沒過大影響

    Cloneable:能夠使用Clone()方法,在HashMap中,實現的是淺層次拷貝,即對拷貝對象的改變會影響被拷貝的對象。

    Serializable:能夠使之序列化,即可以將HashMap對象儲存至本地,之後可以恢複狀態。

2.2、HashMap類的屬性
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {    // 序號    private static final long serialVersionUID = 362498820763181265L;        // 預設的初始容量是16    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;       // 最大容量    static final int MAXIMUM_CAPACITY = 1 << 30;     // 預設的填滿因數    static final float DEFAULT_LOAD_FACTOR = 0.75f;    // 當桶(bucket)上的結點數大於這個值時會轉成紅/黑樹狀結構    static final int TREEIFY_THRESHOLD = 8;     // 當桶(bucket)上的結點數小於這個值時樹轉鏈表    static final int UNTREEIFY_THRESHOLD = 6;    // 桶中結構轉化為紅/黑樹狀結構對應的table的最小大小    static final int MIN_TREEIFY_CAPACITY = 64;    // 儲存元素的數組,總是2的冪次倍    transient Node<k,v>[] table;     // 存放具體元素的集    transient Set<map.entry<k,v>> entrySet;    // 存放元素的個數,注意這個不等於數組的長度。    transient int size;    // 每次擴容和更改map結構的計數器    transient int modCount;       // 臨界值 當實際大小(容量*填滿因數)超過臨界值時,會進行擴容    int threshold;    // 填滿因數    final float loadFactor;}
2.3、HashMap的構造方法

  有四個構造方法,構造方法的作用就是記錄一下16這個數給threshold(這個數值最終會當作第一次數組的長度。)和初始化載入因子。注意,hashMap中table數組一開始就已經是個沒有長度的數組了。

  構造方法中,並沒有初始化數組的大小,數組在一開始就已經被建立了,構造方法只做兩件事情,一個是初始化載入因子,另一個是用threshold記錄下數組初始化的大小。注意是記錄。

  

三、HashMap源碼分析(二)3.1、put方法
   /* 
     * 1. 通過key的hash值確定table下標
     * 2. 尋找table下標,如果key存在則更新對應的value
     * 3. 如果key不存在則調用addEntry()方法
     */  
    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }

        if (key == null)
            return putForNullKey(value);

        int hash = hash(key);
        int i = indexFor(hash, table.length);
        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;
            }
        }
        // key不存在時,加入新元素  
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

 

3.2、get方法
final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }
3.3、resize方法
 /**     * 擴充到指定的大小      */    void resize(int newCapacity) {        Entry[] oldTable = table;        int oldCapacity = oldTable.length;        if (oldCapacity == MAXIMUM_CAPACITY) {            threshold = Integer.MAX_VALUE;            return;        }        Entry[] newTable = new Entry[newCapacity];        transfer(newTable, initHashSeedAsNeeded(newCapacity));        table = newTable;        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);    }
四、總結4.1、關於數組擴容

  從putVal原始碼中我們可以知道,當插入一個元素的時候size就加1,若size大於threshold的時候,就會進行擴容。假設我們的capacity大小為32,loadFator為0.75,則threshold為24 = 32 * 0.75,

  此時,插入了25個元素,並且插入的這25個元素都在同一個桶中,桶中的資料結構為紅/黑樹狀結構,則還有31個桶是空的,也會進行擴容處理,其實,此時,還有31個桶是空的,好像似乎不需要進行擴容處理,

  但是是需要擴容處理的,因為此時我們的capacity大小可能不適當。我們前面知道,擴容處理會遍曆所有的元素,時間複雜度很高;前面我們還知道,經過一次擴容處理後,元素會更加均勻的分布在各個桶中,

  會提升訪問效率。所以,說盡量避免進行擴容處理,也就意味著,遍曆元素所帶來的壞處大於元素在桶中均勻分布所帶來的好處。 

4.2、總結

  1)要知道hashMap在JDK1.8以前是一個鏈表散列這樣一個資料結構,而在JDK1.8以後是一個數組加鏈表加紅/黑樹狀結構的資料結構。

  2)通過源碼的學習,hashMap是一個能快速通過key擷取到value值得一個集合,原因是內部使用的是hash尋找值得方法。

 

Java集合源碼分析--HashMap

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.