中文字幕精品亚洲无线码二区,国产黄a三级三级三级看三级,亚洲七七久久桃花影院,丰满少妇被猛烈进入,国产小视频在线观看网站

Java集合:HashMap源碼剖析(xi)

 

一、HashMap概述
二、HashMap的數據結構
三、HashMap源碼分析
     1、關鍵屬性
     2、構造方法
     3、存儲數據
     4、調整大小

     5、數據讀取

              6、HashMap的性能參數
              7、Fail-Fast機制

 

一、HashMap概述

  HashMap基于哈希表的 Map 接口的實現。此實現提供所有可選的映射操作,并允許使用 null 值和 null 鍵。(除了不同(tong)(tong)步和允許使用 null 之外,HashMap 類與(yu) Hashtable 大(da)致(zhi)相同(tong)(tong)。)此類不保證映射的順序,特別是它不保證該順序恒久不變。

  值(zhi)得(de)(de)注意的是(shi)(shi)HashMap不是(shi)(shi)線(xian)程安全(quan)的,如果(guo)想(xiang)要線(xian)程安全(quan)的HashMap,可以通過Collections類(lei)的靜態(tai)方(fang)法synchronizedMap獲(huo)得(de)(de)線(xian)程安全(quan)的HashMap。

 Map map = Collections.synchronizedMap(new HashMap());

 

二、HashMap的數據結構

  HashMap的底層主要是基于數(shu)組和鏈表來實現的,它之所以有相當快的查詢速度主要是因為它是通過計(ji)算散列碼來(lai)決定存儲的位置。HashMap中主要是通過key的hashCode來計算hash值的,只要hashCode相同,計算出來的hash值就一樣。如果存儲的對象對多了,就有可能不同的對象所算出來的hash值是相同的,這就出現了所謂的hash沖突。學過數據結構的同學都知道,解決hash沖突的方法有很多,HashMap底層是通過鏈表來解決hash沖突的。

 

 圖中(zhong)(zhong),0~15部分即代表哈(ha)(ha)希(xi)表,也(ye)稱為哈(ha)(ha)希(xi)數組,數組的(de)(de)(de)每個元(yuan)素都(dou)是一個單鏈表的(de)(de)(de)頭節點,鏈表是用來解決(jue)沖突的(de)(de)(de),如(ru)果(guo)不(bu)同的(de)(de)(de)key映(ying)射到了數組的(de)(de)(de)同一位置(zhi)處,就將(jiang)其放入單鏈表中(zhong)(zhong)。

 

從上圖我們可以發現哈希表是由數組+鏈表組成的,一個長度為16的數組中,每個元素存儲的是一個鏈表的頭結點Bucket桶。那么這些元素是按照什么樣的規則存儲到數組中呢。一般情況是通過hash(key)%len獲得,也就是元素的key的哈希值對數組長度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存儲在數組下標為12的位置。

 

HashMap其實也是一個線性的數組實現的,所以可以理解為其存儲數據的容器就是一個線性數組。這可能讓我們很不解,一個線性的數組怎么實現按鍵值對來存取數據呢?這里HashMap有做一些處理。

 

  首先HashMap里面實現一個靜態內部類Entry,其重要的屬性有 key , value, next,從屬性key,value我們就能很明顯的看出來Entry就是HashMap鍵值對實現的一個基礎bean,我們上面說到HashMap的基礎就是一個線性數組,這個數組就是Entry[],Map里面的內容都保存在Entry[]里面。

 

我們看看HashMap中(zhong)Entry類的代碼:

 

    /** Entry是單向鏈表。    
     * 它是 “HashMap鏈式(shi)存儲(chu)法”對應的鏈表。    
     *它實現了Map.Entry 接口,即實現getKey(), getValue(), setValue(V value), equals(Object o), hashCode()這些函數  
    **/  
    static class Entry<K,V> implements Map.Entry<K,V> {    
        final K key;    
        V value;    
        // 指向(xiang)下(xia)一個節(jie)點(dian)    
        Entry<K,V> next;    
        final int hash;    
   
        // 構造函數。    
        // 輸入參數包括"哈希值(h)", "鍵(k)", "值(v)", "下(xia)一節點(n)"    
        Entry(int h, K k, V v, Entry<K,V> n) {    
            value = v;    
            next = n;    
            key = k;    
            hash = h;    
        }    
   
        public final K getKey() {    
            return key;    
        }    
   
        public final V getValue() {    
            return value;    
        }    
   
        public final V setValue(V newValue) {    
            V oldValue = value;    
            value = newValue;    
            return oldValue;    
        }    
   
        // 判斷兩個Entry是(shi)否相等(deng)    
        // 若兩(liang)個Entry的“key”和“value”都相等,則返回true。    
        // 否則,返回false    
        public final boolean equals(Object o) {    
            if (!(o instanceof Map.Entry))    
                return false;    
            Map.Entry e = (Map.Entry)o;    
            Object k1 = getKey();    
            Object k2 = e.getKey();    
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {    
                Object v1 = getValue();    
                Object v2 = e.getValue();    
                if (v1 == v2 || (v1 != null && v1.equals(v2)))    
                    return true;    
            }    
            return false;    
        }    
   
        // 實現hashCode()    
        public final int hashCode() {    
            return (key==null   ? 0 : key.hashCode()) ^    
                   (value==null ? 0 : value.hashCode());    
        }    
   
        public final String toString() {    
            return getKey() + "=" + getValue();    
        }    
   
        // 當向(xiang)HashMap中添加元(yuan)素時,繪調(diao)用recordAccess()。    
        // 這(zhe)里不做任(ren)何處理    
        void recordAccess(HashMap<K,V> m) {    
        }    
   
        // 當從(cong)HashMap中刪除元素時(shi),繪調用recordRemoval()。    
        // 這里不做任何(he)處理    
        void recordRemoval(HashMap<K,V> m) {    
        }    
    }

 

HashMap其實就是(shi)一個(ge)Entry數組,Entry對(dui)象中(zhong)包含了(le)鍵和值,其中(zhong)next也是(shi)一個(ge)Entry對(dui)象,它就是(shi)用來處理hash沖突的(de),形成一個(ge)鏈表。

 

三、HashMap源碼分析

 

       1、關鍵屬性

  先看看HashMap類(lei)中的一些關鍵屬性:

 

 transient Entry[] table;//存儲(chu)元素(su)的實體(ti)數組
  
 transient int size;//存(cun)放元素的個數
  
 int threshold; //臨(lin)界值   當(dang)實際大小超過臨(lin)界值時,會進行(xing)擴容(rong)threshold = 加載因子(zi)*容(rong)量
 
 final float loadFactor; //加載(zai)因子(zi)
  
 transient int modCount;//被修改的次數

 

其中loadFactor加載因子是表示Hsah表中元素的填(tian)滿的程度.

若:加(jia)載因(yin)子(zi)越大,填滿的元素(su)越多(duo),好處(chu)是,空間利用(yong)率高(gao)了(le),但:沖突的機會加(jia)大了(le).鏈表長度會越來越長,查找效率降低。

反之,加(jia)載因子越小(xiao),填滿的元素(su)越少,好(hao)處是(shi):沖突(tu)的機會(hui)減小(xiao)了(le),但(dan):空間(jian)浪費多了(le).表(biao)中(zhong)的數據將過于稀疏(很多空間(jian)還沒用,就開始擴容了(le))

沖突的機會越(yue)大(da),則查找的成本越(yue)高.

因(yin)此,必須在 "沖突的(de)機會(hui)"與"空(kong)間利用率"之間尋找一種平衡與折衷. 這種平衡與折衷本質(zhi)上是數(shu)據結構中有名的(de)"時-空(kong)"矛盾的(de)平衡與折衷.

  如果機器內存足夠,并且想要提高查詢速度的話可以將加載因子設置小一點;相反如果機器內存緊張,并且對查詢速度沒有什么要求的話可以將加載因子設置大一點。不過一般我們都不用去設置它,讓它取默認(ren)值0.75就好了。

 

2、構造方法

下面看看HashMap的幾個構造方法:

 

public HashMap(int initialCapacity, float loadFactor) {
          //確(que)保(bao)數字(zi)合法
          if (initialCapacity < 0)
              throw new IllegalArgumentException("Illegal initial capacity: " +
                                                initialCapacity);
          if (initialCapacity > MAXIMUM_CAPACITY)
              initialCapacity = MAXIMUM_CAPACITY;
          if (loadFactor <= 0 || Float.isNaN(loadFactor))
              throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
 
         // Find a power of 2 >= initialCapacity
         int capacity = 1;   //初始(shi)容量
         while (capacity < initialCapacity)   //確保容(rong)量為(wei)2的(de)(de)(de)n次(ci)(ci)冪(mi),使capacity為(wei)大于initialCapacity的(de)(de)(de)最(zui)小(xiao)的(de)(de)(de)2的(de)(de)(de)n次(ci)(ci)冪(mi)
             capacity <<= 1;
 
         this.loadFactor = loadFactor;
         threshold = (int)(capacity * loadFactor);
         table = new Entry[capacity];
        init();
    }
 
     public HashMap(int initialCapacity) {
         this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
 
     public HashMap() {
         this.loadFactor = DEFAULT_LOAD_FACTOR;
         threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
         table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
     }

 

我(wo)們可以看到在構造(zao)HashMap的(de)時候(hou)如果我(wo)們指定了加載因子和初(chu)始容(rong)(rong)(rong)量(liang)(liang)的(de)話就(jiu)調(diao)用第一個(ge)構造(zao)方法(fa),否則(ze)的(de)話就(jiu)是用默(mo)認的(de)。默(mo)認初(chu)始容(rong)(rong)(rong)量(liang)(liang)為(wei)16,默(mo)認加載因子為(wei)0.75。我(wo)們可以看到上面代碼(ma)中13-15行,這段代碼(ma)的(de)作用是確(que)保容(rong)(rong)(rong)量(liang)(liang)為(wei)2的(de)n次冪(mi),使capacity為(wei)大于initialCapacity的(de)最小(xiao)的(de)2的(de)n次冪(mi),至于為(wei)什么要把容(rong)(rong)(rong)量(liang)(liang)設置為(wei)2的(de)n次冪(mi),我(wo)們等(deng)下(xia)再看。

 

重(zhong)點分析下HashMap中(zhong)用的(de)最多的(de)兩個方法(fa)put和get

       3、存儲數據

  下(xia)面看(kan)看(kan)HashMap存儲數(shu)據的(de)過(guo)程是怎(zen)樣(yang)的(de),首先(xian)看(kan)看(kan)HashMap的(de)put方(fang)法(fa):

  

public V put(K key, V value) {
     // 若“key為null”,則(ze)將(jiang)該鍵值(zhi)對添加(jia)到table[0]中。
         if (key == null) 
            return putForNullKey(value);
     // 若“key不為(wei)null”,則(ze)計算(suan)該key的哈(ha)希值(zhi),然后將其(qi)添加到該哈(ha)希值(zhi)對應(ying)的鏈表中。
         int hash = hash(key.hashCode());
     //搜(sou)索指定hash值在對應table中的(de)索引(yin)
         int i = indexFor(hash, table.length);
     // 循環遍(bian)歷Entry數(shu)組,若“該key”對(dui)應的鍵值(zhi)對(dui)已(yi)經存在,則用(yong)新(xin)的value取(qu)代舊的value。然后退出(chu)!
         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))) { //如果key相同(tong)則覆蓋并返回舊值
                  V oldValue = e.value;
                 e.value = value;
                 e.recordAccess(this);
                 return oldValue;
              }
         }
     //修改次數+1
         modCount++;
     //將key-value添加(jia)到table[i]處
     addEntry(hash, key, value, i);
     return null;
}

 

上面(mian)程(cheng)序(xu)中用到了(le)一個重要的(de)(de)內部(bu)接口:Map.Entry,每個 Map.Entry 其實就是一個 key-value 對(dui)(dui)。從上面(mian)程(cheng)序(xu)中可以(yi)看出:當(dang)系統決(jue)定(ding)存儲 HashMap 中的(de)(de) key-value 對(dui)(dui)時(shi),完全沒有考(kao)慮 Entry 中的(de)(de) value,僅(jin)僅(jin)只是根據 key 來計算(suan)并決(jue)定(ding)每個 Entry 的(de)(de)存儲位(wei)置。這也說明了(le)前面(mian)的(de)(de)結(jie)論:我們完全可以(yi)把 Map 集合(he)中的(de)(de) value 當(dang)成 key 的(de)(de)附屬,當(dang)系統決(jue)定(ding)了(le) key 的(de)(de)存儲位(wei)置之后,value 隨之保(bao)存在那里即可。

我們慢慢的(de)(de)來分(fen)析這個(ge)函數,第2和3行(xing)的(de)(de)作(zuo)用就(jiu)是處理key值(zhi)為null的(de)(de)情況,我們看(kan)看(kan)putForNullKey(value)方法:

 

 private V putForNullKey(V value) {
          for (Entry<K,V> e = table[0]; e != null; e = e.next) {
              if (e.key == null) {   //如果有key為null的對象存在,則覆蓋(gai)掉
                  V oldValue = e.value;
                  e.value = value;
                  e.recordAccess(this);
                  return oldValue;
             }
         }
         modCount++;
         addEntry(0, null, value, 0); //如果鍵(jian)為null的話(hua),則(ze)hash值為0
         return null;
 }

 

注意:如果key為null的話(hua),hash值(zhi)為0,對象(xiang)存儲在數組(zu)中索(suo)引為0的位置。即(ji)table[0]

我(wo)們再回(hui)去看看put方(fang)法(fa)中第4行,它是通過key的hashCode值計算hash碼(ma),下面(mian)是計算hash碼(ma)的函數:

 

  //計算hash值的方法 通過(guo)鍵的hashCode來計算
     static int hash(int h) {
         // This function ensures that hashCodes that differ only by
         // constant multiples at each bit position have a bounded
         // number of collisions (approximately 8 at default load factor).
         h ^= (h >>> 20) ^ (h >>> 12);
         return h ^ (h >>> 7) ^ (h >>> 4);
     }

 

得到(dao)hash碼之后就會通過hash碼去(qu)計(ji)(ji)算出(chu)應該存儲在數組中的索(suo)引(yin)(yin),計(ji)(ji)算索(suo)引(yin)(yin)的函數如(ru)下(xia):

 

     static int indexFor(int h, int length) { //根據hash值(zhi)(zhi)和數組(zu)長度算出索引值(zhi)(zhi)
         return h & (length-1);  //這里不(bu)能(neng)隨(sui)便算(suan)取(qu),用hash&(length-1)是(shi)有原因的,這樣(yang)可以確保算(suan)出來(lai)的索引(yin)是(shi)在數組大小范圍內,不(bu)會(hui)超出
     }

 

這(zhe)個我(wo)們要(yao)重點說(shuo)下,我(wo)們一(yi)般(ban)對哈希(xi)表的(de)(de)散列(lie)很(hen)自然地會(hui)想到用hash值對length取(qu)模(mo)(即除(chu)法(fa)(fa)散列(lie)法(fa)(fa)),Hashtable中(zhong)也是這(zhe)樣(yang)實(shi)現的(de)(de),這(zhe)種方(fang)法(fa)(fa)基本(ben)能保證元素在哈希(xi)表中(zhong)散列(lie)的(de)(de)比較均(jun)勻,但取(qu)模(mo)會(hui)用到除(chu)法(fa)(fa)運算,效率(lv)很(hen)低,HashMap中(zhong)則通過h&(length-1)的(de)(de)方(fang)法(fa)(fa)來(lai)代替取(qu)模(mo),同樣(yang)實(shi)現了均(jun)勻的(de)(de)散列(lie),但效率(lv)要(yao)高很(hen)多,這(zhe)也是HashMap對Hashtable的(de)(de)一(yi)個改進。

 

    接下來,我們分析下為(wei)什么哈希表(biao)的(de)容(rong)量一定要是(shi)2的(de)整數(shu)(shu)(shu)(shu)次(ci)(ci)冪(mi)。首先,length為(wei)2的(de)整數(shu)(shu)(shu)(shu)次(ci)(ci)冪(mi)的(de)話,h&(length-1)就相當于對(dui)length取(qu)模,這(zhe)(zhe)(zhe)樣(yang)便(bian)(bian)保證了(le)散(san)列(lie)(lie)的(de)均(jun)(jun)勻,同時也提升(sheng)了(le)效率;其次(ci)(ci),length為(wei)2的(de)整數(shu)(shu)(shu)(shu)次(ci)(ci)冪(mi)的(de)話,為(wei)偶(ou)數(shu)(shu)(shu)(shu),這(zhe)(zhe)(zhe)樣(yang)length-1為(wei)奇(qi)數(shu)(shu)(shu)(shu),奇(qi)數(shu)(shu)(shu)(shu)的(de)最(zui)后(hou)一位(wei)(wei)是(shi)1,這(zhe)(zhe)(zhe)樣(yang)便(bian)(bian)保證了(le)h&(length-1)的(de)最(zui)后(hou)一位(wei)(wei)可(ke)能(neng)(neng)為(wei)0,也可(ke)能(neng)(neng)為(wei)1(這(zhe)(zhe)(zhe)取(qu)決于h的(de)值(zhi)),即(ji)與(yu)后(hou)的(de)結果可(ke)能(neng)(neng)為(wei)偶(ou)數(shu)(shu)(shu)(shu),也可(ke)能(neng)(neng)為(wei)奇(qi)數(shu)(shu)(shu)(shu),這(zhe)(zhe)(zhe)樣(yang)便(bian)(bian)可(ke)以保證散(san)列(lie)(lie)的(de)均(jun)(jun)勻性,而如果length為(wei)奇(qi)數(shu)(shu)(shu)(shu)的(de)話,很明顯length-1為(wei)偶(ou)數(shu)(shu)(shu)(shu),它的(de)最(zui)后(hou)一位(wei)(wei)是(shi)0,這(zhe)(zhe)(zhe)樣(yang)h&(length-1)的(de)最(zui)后(hou)一位(wei)(wei)肯定為(wei)0,即(ji)只(zhi)(zhi)能(neng)(neng)為(wei)偶(ou)數(shu)(shu)(shu)(shu),這(zhe)(zhe)(zhe)樣(yang)任何hash值(zhi)都只(zhi)(zhi)會(hui)被散(san)列(lie)(lie)到數(shu)(shu)(shu)(shu)組的(de)偶(ou)數(shu)(shu)(shu)(shu)下標位(wei)(wei)置(zhi)上,這(zhe)(zhe)(zhe)便(bian)(bian)浪費了(le)近一半的(de)空間(jian),因此,length取(qu)2的(de)整數(shu)(shu)(shu)(shu)次(ci)(ci)冪(mi),是(shi)為(wei)了(le)使不(bu)同hash值(zhi)發生碰撞的(de)概率較(jiao)小,這(zhe)(zhe)(zhe)樣(yang)就能(neng)(neng)使元(yuan)素在哈希表(biao)中均(jun)(jun)勻地散(san)列(lie)(lie)。

 

  這(zhe)看(kan)上(shang)去(qu)很簡單,其實比較有玄機(ji)的,我們舉個例子來說明:

  假設數組長度分別為15和16,優化后的hash碼分別為8和9,那么&運算后的結果如下: 

 

         h & (table.length-1)                     hash                             table.length-1
       8 & (15-1):                                 1000                   &              1110                   =                1000
       9 & (15-1):                                 1001                   &              1110                   =                1000
       -----------------------------------------------------------------------------------------------------------------------
       8 & (16-1):                                 1000                   &              1111                   =                1000
       9 & (16-1):                                 1001                   &              1111                   =                1001

 

 

 

 

  從(cong)上面的例子中可以看(kan)出:

  當(dang)(dang)它們和(he)15-1(1110)“與(yu)”的(de)(de)(de)(de)時(shi)候(hou)(hou),產(chan)生了(le)相(xiang)同(tong)的(de)(de)(de)(de)結果,也就是(shi)(shi)說它們會定位(wei)到(dao)數(shu)(shu)組(zu)中的(de)(de)(de)(de)同(tong)一(yi)(yi)(yi)(yi)個(ge)位(wei)置上去,這(zhe)就產(chan)生了(le)碰(peng)(peng)撞,8和(he)9會被放(fang)到(dao)數(shu)(shu)組(zu)中的(de)(de)(de)(de)同(tong)一(yi)(yi)(yi)(yi)個(ge)位(wei)置上形成(cheng)鏈(lian)表,那(nei)(nei)么查詢(xun)的(de)(de)(de)(de)時(shi)候(hou)(hou)就需(xu)要遍歷這(zhe)個(ge)鏈(lian)表,得到(dao)8或者9,這(zhe)樣就降低(di)了(le)查詢(xun)的(de)(de)(de)(de)效率。同(tong)時(shi),我(wo)們也可以發現(xian),當(dang)(dang)數(shu)(shu)組(zu)長度為(wei)15的(de)(de)(de)(de)時(shi)候(hou)(hou),hash值會與(yu)15-1(1110)進行“與(yu)”,那(nei)(nei)么 最后一(yi)(yi)(yi)(yi)位(wei)永遠(yuan)是(shi)(shi)0,而0001,0011,0101,1001,1011,0111,1101這(zhe)幾個(ge)位(wei)置永遠(yuan)都不能(neng)存(cun)放(fang)元素了(le),空間浪費相(xiang)當(dang)(dang)大,更糟的(de)(de)(de)(de)是(shi)(shi)這(zhe)種情況中,數(shu)(shu)組(zu)可以使用的(de)(de)(de)(de)位(wei)置比數(shu)(shu)組(zu)長度小了(le)很多,這(zhe)意(yi)味著(zhu)進一(yi)(yi)(yi)(yi)步增加了(le)碰(peng)(peng)撞的(de)(de)(de)(de)幾率,減慢了(le)查詢(xun)的(de)(de)(de)(de)效率!

  而當(dang)數(shu)組長度為(wei)16時(shi)(shi),即為(wei)2的(de)(de)n次(ci)方時(shi)(shi),2n-1得到的(de)(de)二(er)進(jin)制數(shu)的(de)(de)每(mei)個位上的(de)(de)值都(dou)為(wei)1,這使得在低(di)位上&時(shi)(shi),得到的(de)(de)和原hash的(de)(de)低(di)位相同,加之hash(int h)方法對key的(de)(de)hashCode的(de)(de)進(jin)一步(bu)優(you)化(hua),加入了高位計算,就使得只有(you)相同的(de)(de)hash值的(de)(de)兩個值才會(hui)被放到數(shu)組中的(de)(de)同一個位置(zhi)上形成鏈(lian)表(biao)。

   所以說,當數組(zu)長度(du)為2的(de)(de)n次冪的(de)(de)時候(hou)(hou),不同的(de)(de)key算得得index相同的(de)(de)幾(ji)率(lv)(lv)較小,那(nei)么(me)數據在數組(zu)上分布就(jiu)比較均勻(yun),也就(jiu)是說碰撞(zhuang)的(de)(de)幾(ji)率(lv)(lv)小,相對的(de)(de),查詢(xun)的(de)(de)時候(hou)(hou)就(jiu)不用遍歷(li)某個(ge)位(wei)置(zhi)上的(de)(de)鏈表,這樣查詢(xun)效率(lv)(lv)也就(jiu)較高了。

   

   ; 根據上面(mian) put 方法的(de)(de)源代碼可以看出(chu),當程序(xu)(xu)試圖(tu)將一(yi)個key-value對放(fang)入HashMap中時,程序(xu)(xu)首先根據該 key 的(de)(de) hashCode() 返(fan)回(hui)值決定該 Entry 的(de)(de)存儲位置:

  •   如果兩個 Entry 的 key 的 hashCode() 返回(hui)值相(xiang)同,那它們的存儲位(wei)置(zhi)相(xiang)同。
  •   如果(guo)這(zhe)兩個 Entry 的 key 通過 equals 比較返(fan)回 true,新添加 Entry 的 value 將覆(fu)蓋(gai)(gai)集合(he)中原有 Entry 的 value,但key不會(hui)覆(fu)蓋(gai)(gai)。  
  •   如果(guo)這兩個 Entry 的(de) key 通(tong)過 equals 比較返回 false,新添加的(de) Entry 將與集合中原有 Entry 形成(cheng) Entry 鏈,而且新添加的(de) Entry 位于 Entry 鏈的(de)頭部——

  具(ju)體說(shuo)明繼續看 addEntry() 方法的說(shuo)明。

 

 void addEntry(int hash, K key, V value, int bucketIndex) {
         Entry<K,V> e = table[bucketIndex]; //如果要加入(ru)的(de)位(wei)置(zhi)(zhi)有值(zhi),將(jiang)該位(wei)置(zhi)(zhi)原先(xian)的(de)值(zhi)設置(zhi)(zhi)為新entry的(de)next,也就是新entry鏈(lian)表(biao)的(de)下一個節點
         table[bucketIndex] = new Entry<>(hash, key, value, e);
         if (size++ >= threshold) //如果大于臨界值就擴容(rong)
             resize(2 * table.length); //以(yi)2的(de)倍數擴容
 }

 

參數(shu)bucketIndex就(jiu)是indexFor函(han)數(shu)計(ji)算出來的(de)索引值(zhi),第(di)2行代碼是取得數(shu)組中索引為bucketIndex的(de)Entry對(dui)(dui)象,第(di)3行就(jiu)是用(yong)hash、key、value構(gou)建一(yi)個新(xin)的(de)Entry對(dui)(dui)象放到索引為bucketIndex的(de)位(wei)置,并且將該位(wei)置原先(xian)的(de)對(dui)(dui)象設置為新(xin)對(dui)(dui)象的(de)next構(gou)成鏈(lian)表。

  第4行和第5行就(jiu)是(shi)(shi)判斷(duan)put后size是(shi)(shi)否達到了臨界值threshold,如果(guo)達到了臨界值就(jiu)要(yao)進行擴容,HashMap擴容是(shi)(shi)擴為原來的兩(liang)倍(bei)。

 

4、調整大小

resize()方法(fa)如下(xia):

 重新調(diao)整HashMap的(de)大小,newCapacity是調(diao)整后的(de)單位

     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);//用來將原先table的元素全部移到(dao)newTable里面(mian)
         table = newTable;  //再將newTable賦值給table
         threshold = (int)(newCapacity * loadFactor);//重(zhong)新計算臨界值(zhi)
     }

 

  新(xin)建了一個(ge)HashMap的(de)底層數組(zu),上面代碼中第(di)10行為調用transfer方法,將HashMap的(de)全部元(yuan)素添加到(dao)新(xin)的(de)HashMap中,并重新(xin)計算元(yuan)素在新(xin)的(de)數組(zu)中的(de)索引位置(zhi)

 

  當HashMap中的元素越來越多的時候,hash沖突的幾率也就越來越高,因為數組的長度是固定的。所以為了提高查詢的效率,就要對HashMap的數組進行擴容,數組擴容這個操作也會出現在ArrayList中,這是一個常用的操作,而在HashMap數組擴容之后,最消耗性能的點就出現了:原數(shu)(shu)組中(zhong)的數(shu)(shu)據必須(xu)重新(xin)計算其(qi)在新(xin)數(shu)(shu)組中(zhong)的位(wei)置(zhi),并(bing)放(fang)進去,這就(jiu)是(shi)resize

 

   那(nei)么(me)HashMap什么(me)時(shi)(shi)候進(jin)(jin)行(xing)擴(kuo)(kuo)(kuo)容(rong)呢?當(dang)HashMap中的(de)(de)(de)元(yuan)素(su)(su)(su)個(ge)(ge)(ge)數(shu)(shu)超過(guo)數(shu)(shu)組(zu)(zu)(zu)大小*loadFactor時(shi)(shi),就會進(jin)(jin)行(xing)數(shu)(shu)組(zu)(zu)(zu)擴(kuo)(kuo)(kuo)容(rong),loadFactor的(de)(de)(de)默認值為0.75,這(zhe)是(shi)一個(ge)(ge)(ge)折中的(de)(de)(de)取值。也就是(shi)說,默認情況下,數(shu)(shu)組(zu)(zu)(zu)大小為16,那(nei)么(me)當(dang)HashMap中元(yuan)素(su)(su)(su)個(ge)(ge)(ge)數(shu)(shu)超過(guo)16*0.75=12的(de)(de)(de)時(shi)(shi)候,就把數(shu)(shu)組(zu)(zu)(zu)的(de)(de)(de)大小擴(kuo)(kuo)(kuo)展為 2*16=32,即擴(kuo)(kuo)(kuo)大一倍,然后重新計算每(mei)個(ge)(ge)(ge)元(yuan)素(su)(su)(su)在數(shu)(shu)組(zu)(zu)(zu)中的(de)(de)(de)位置,擴(kuo)(kuo)(kuo)容(rong)是(shi)需要(yao)進(jin)(jin)行(xing)數(shu)(shu)組(zu)(zu)(zu)復制的(de)(de)(de),復制數(shu)(shu)組(zu)(zu)(zu)是(shi)非常消耗性能的(de)(de)(de)操作(zuo),所(suo)以如果我們已(yi)經預知HashMap中元(yuan)素(su)(su)(su)的(de)(de)(de)個(ge)(ge)(ge)數(shu)(shu),那(nei)么(me)預設元(yuan)素(su)(su)(su)的(de)(de)(de)個(ge)(ge)(ge)數(shu)(shu)能夠有效的(de)(de)(de)提高HashMap的(de)(de)(de)性能。

 

 

 5、數據讀取

 

 

public V get(Object key) {   
    if (key == null)   
        return getForNullKey();   
    int hash = hash(key.hashCode());   
    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.equals(k)))   
            return e.value;   
    }   
    return null;   
}  

  有了(le)上面存(cun)儲(chu)時的(de)hash算法作(zuo)為基礎(chu),理解起(qi)來這段代(dai)碼(ma)就很(hen)容(rong)易了(le)。從(cong)上面的(de)源代(dai)碼(ma)中可以(yi)看(kan)出:從(cong)HashMap中get元(yuan)素時,首先計算key的(de)hashCode,找(zhao)到(dao)(dao)數(shu)組中對應位(wei)置(zhi)的(de)某一元(yuan)素,然后通過key的(de)equals方法在對應位(wei)置(zhi)的(de)鏈(lian)表中找(zhao)到(dao)(dao)需要(yao)的(de)元(yuan)素。

 

   歸納起(qi)來簡單地(di)說,HashMap 在(zai)底層(ceng)將 key-value 當(dang)成一個(ge)(ge)(ge)整(zheng)體進行(xing)處理(li),這個(ge)(ge)(ge)整(zheng)體就是(shi)一個(ge)(ge)(ge) Entry&nbsp;對象(xiang)(xiang)。HashMap 底層(ceng)采用一個(ge)(ge)(ge) Entry[] 數(shu)組(zu)(zu)來保存(cun)所有的(de) key-value 對,當(dang)需要存(cun)儲(chu)一個(ge)(ge)(ge) Entry 對象(xiang)(xiang)時,會(hui)根據(ju)(ju)hash算法(fa)來決(jue)定(ding)(ding)其(qi)在(zai)數(shu)組(zu)(zu)中(zhong)的(de)存(cun)儲(chu)位置,在(zai)根據(ju)(ju)equals方法(fa)決(jue)定(ding)(ding)其(qi)在(zai)該數(shu)組(zu)(zu)位置上的(de)鏈表(biao)中(zhong)的(de)存(cun)儲(chu)位置;當(dang)需要取出(chu)一個(ge)(ge)(ge)Entry時,也會(hui)根據(ju)(ju)hash算法(fa)找到其(qi)在(zai)數(shu)組(zu)(zu)中(zhong)的(de)存(cun)儲(chu)位置,再根據(ju)(ju)equals方法(fa)從該位置上的(de)鏈表(biao)中(zhong)取出(chu)該Entry。

 

6、HashMap的性能參數:

 

   HashMap 包含如下幾個構造器:

   HashMap():構建一個初(chu)始容量(liang)為 16,負載因(yin)子為 0.75 的 HashMap。

   HashMap(int initialCapacity):構建一個初始容(rong)量為(wei)(wei) initialCapacity,負載因子為(wei)(wei) 0.75 的 HashMap。

   HashMap(int initialCapacity, float loadFactor):以指定(ding)初始容(rong)量、指定(ding)的(de)負載因子創建一(yi)個 HashMap。

   HashMap的基(ji)礎(chu)構造器HashMap(int initialCapacity, float loadFactor)帶(dai)有兩個(ge)參數(shu),它們是初始容量initialCapacity和加載因子loadFactor。

   initialCapacity:HashMap的最大容量,即為底層(ceng)數組的長度(du)。

   loadFactor:負(fu)載因子loadFactor定義為:散(san)列表的實際元素數目(n)/ 散(san)列表的容量(m)。

   負(fu)(fu)載(zai)因(yin)子衡量的(de)(de)是一(yi)個(ge)散(san)列(lie)表(biao)(biao)的(de)(de)空間(jian)(jian)的(de)(de)使用程(cheng)度(du),負(fu)(fu)載(zai)因(yin)子越大(da)表(biao)(biao)示散(san)列(lie)表(biao)(biao)的(de)(de)裝(zhuang)填程(cheng)度(du)越高,反之愈小。對(dui)(dui)于使用鏈表(biao)(biao)法的(de)(de)散(san)列(lie)表(biao)(biao)來(lai)說,查找(zhao)一(yi)個(ge)元素的(de)(de)平均時間(jian)(jian)是O(1+a),因(yin)此如果(guo)負(fu)(fu)載(zai)因(yin)子越大(da),對(dui)(dui)空間(jian)(jian)的(de)(de)利(li)用更充分(fen),然而后果(guo)是查找(zhao)效率(lv)的(de)(de)降低(di);如果(guo)負(fu)(fu)載(zai)因(yin)子太(tai)小,那(nei)么散(san)列(lie)表(biao)(biao)的(de)(de)數據將過于稀疏,對(dui)(dui)空間(jian)(jian)造成嚴重浪費(fei)。

   HashMap的實(shi)現(xian)中,通過(guo)threshold字段來判斷HashMap的最大容量:

 

threshold = (int)(capacity * loadFactor);  

   結合負(fu)載因子的(de)定義公式(shi)可知,threshold就是(shi)(shi)在(zai)此loadFactor和(he)(he)capacity對應下允許(xu)的(de)最(zui)大(da)元素數目(mu),超過這個(ge)數目(mu)就重新resize,以降低實際的(de)負(fu)載因子。默認的(de)的(de)負(fu)載因子0.75是(shi)(shi)對空間和(he)(he)時間效率(lv)的(de)一個(ge)平衡(heng)選擇。當容(rong)量(liang)超出此最(zui)大(da)容(rong)量(liang)時, resize后的(de)HashMap容(rong)量(liang)是(shi)(shi)容(rong)量(liang)的(de)兩(liang)倍:

 

if (size++ >= threshold)      
    resize(2 * table.length);

7、Fail-Fast機制:

&nbsp;  我們知道java.util.HashMap不是線程安全(quan)的(de),因(yin)此如(ru)果在使用迭代器的(de)過程中有其(qi)他(ta)線程修改了map,那么(me)將拋出ConcurrentModificationException,這(zhe)就(jiu)是所謂fail-fast策略(lve)。

   這一策略在(zai)源碼中(zhong)的實現是(shi)通過modCount域,modCount顧名(ming)思(si)義就是(shi)修改(gai)次數,對HashMap內容的修改(gai)都將增加這個值(zhi),那么在(zai)迭(die)代器初始(shi)化過程中(zhong)會將這個值(zhi)賦給迭(die)代器的expectedModCount。

private abstract class HashIterator<E> implements Iterator<E> {
        Entry<K,V> next;    // next entry to return
        int expectedModCount;    // For fast-fail
        int index;        // current slot
        Entry<K,V> current;    // current entry

        HashIterator() {
            expectedModCount = modCount;
            if (size > 0) { // advance to first entry
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
        }

        public final boolean hasNext() {
            return next != null;
        }

        final Entry<K,V> nextEntry() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Entry<K,V> e = next;
            if (e == null)
                throw new NoSuchElementException();

            if ((next = e.next) == null) {
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
        current = e;
            return e;
        }

        public void remove() {
            if (current == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Object k = current.key;
            current = null;
            HashMap.this.removeEntryForKey(k);
            expectedModCount = modCount;
        }

    }

 

在(zai)迭代過程中,判斷modCount跟(gen)expectedModCount是否相等(deng),如果不相等(deng)就表示已經有(you)其(qi)他線程修(xiu)改了Map:

   注意到(dao)modCount聲明為volatile,保(bao)證線程之間修改的可見性(xing)。

final Entry<K,V> nextEntry() {      
    if (modCount != expectedModCount)      
        throw new ConcurrentModificationException();  

在HashMap的API中(zhong)指出:

   由所(suo)有HashMap類的(de)(de)(de)(de)“collection 視圖方法”所(suo)返(fan)回的(de)(de)(de)(de)迭(die)代器都是快(kuai)速失(shi)敗的(de)(de)(de)(de):在(zai)迭(die)代器創(chuang)建之后,如果從結構上(shang)對映射進行修(xiu)改(gai),除非(fei)通過迭(die)代器本(ben)身(shen)的(de)(de)(de)(de) remove 方法,其他任何時(shi)間(jian)任何方式的(de)(de)(de)(de)修(xiu)改(gai),迭(die)代器都將拋出 ConcurrentModificationException。因(yin)此,面(mian)對并(bing)發的(de)(de)(de)(de)修(xiu)改(gai),迭(die)代器很快(kuai)就會完全失(shi)敗,而不(bu)冒(mao)在(zai)將來不(bu)確定的(de)(de)(de)(de)時(shi)間(jian)發生任意不(bu)確定行為的(de)(de)(de)(de)風險。

   注(zhu)意,迭代器的(de)(de)快速(su)失敗行為不(bu)能(neng)得(de)到保證,一般來說,存在非(fei)同(tong)步的(de)(de)并發修改時,不(bu)可能(neng)作出(chu)任何堅(jian)決的(de)(de)保證。快速(su)失敗迭代器盡(jin)最大努(nu)力拋(pao)出(chu) ConcurrentModificationException。因此(ci),編寫依賴于此(ci)異(yi)常的(de)(de)程序的(de)(de)做法是(shi)(shi)錯誤(wu)的(de)(de),正確(que)做法是(shi)(shi):迭代器的(de)(de)快速(su)失敗行為應該僅用于檢(jian)測(ce)程序錯誤(wu)

 

參考鏈(lian)接:

深入Java集合學習系列:HashMap的實現原理

posted @ 2014-08-31 23:28  ^_TONY_^  閱讀(66300)  評論(16)    收藏  舉報