莊周夢蝶

          生活、程序、未來
             :: 首頁 ::  ::  :: 聚合  :: 管理

          java.util.HashMap源碼要點淺析

          Posted on 2009-04-15 12:33 dennis 閱讀(4387) 評論(0)  編輯  收藏 所屬分類: java數據結構與算法源碼解讀
          1、散列表要解決的一個問題就是散列值的沖突問題,通常是兩種方法:鏈表法和開放地址法。鏈表法就是將相同hash值的對象組織成一個鏈表放在hash值對應的槽位;開放地址法是通過一個探測算法,當某個槽位已經被占據的情況下繼續查找下一個可以使用的槽位。java.util.HashMap采用的鏈表法的方式,鏈表是單向鏈表,因此在刪除過程中要自己維持prev節點,我想不采用雙向鏈表是從節省空間考慮。一個典型的查找過程:
          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;
           }
             HashMap采用鏈表法而不是開放地址法,猜想可能的原因是從實用角度出發,對空間和時間效率做出的折中選擇。采用開放地址法,無論是線性探測或者二次探測都可能造成群集現象,而雙重散列會要求散列表的裝填程度比較低的情況下會有比較好的查找效率,容易造成空間的浪費。

          2、什么是負載因子?負載因子a定義為
               a=散列表的實際元素數目(n)/ 散列表的容量(m)

          負載因子衡量的是一個散列表的空間的使用程度,負載因子越大表示散列表的裝填程度越高,反之愈小。對于使用鏈表法的散列表來說,查找一個元素的平均時間是 O(1+a),因此如果負載因子越大,對空間的利用更充分,然而后果是查找效率的降低;如果負載因子太小,那么散列表的數據將過于稀疏,對空間造成嚴重浪費。

          回到HashMap的實現,HashMap中的loadFactor其實定義的就是該map對象允許的最大的負載因子,如果超過這個系數將重新resize。這個是通過threshold字段來判斷,看threshold的計算:
          threshold = (int)(capacity * loadFactor);

          結合上面的負載因子的定義公式可知,threshold就是在此loadFactor和capacity對應下允許的最大元素數目,超過這個數目就重新resize,以降低實際的負載因子。默認的的負載因子0.75是對空間和時間效率的一個平衡選擇。注意到的一點是resize的規模是現有 capacity的兩倍:
            if (size++ >= threshold)
                      resize(
          2 * table.length);
           
          3、可能你也注意到了,java.util.HashMap對key的hash值多做了一步處理,而不是直接使用hashCode:

          static int hash(int h) {
                  h 
          ^= (h >>> 20^ (h >>> 12);
                  
          return h ^ (h >>> 7^ (h >>> 4);
            }

          這個處理的原因在于HashMap的容量總是采用2的p次冪,而取index(槽位)的方法是
          static int indexFor(int h, int length) {
                  
          return h & (length-1);
           }

          這一運算等價于對length取模,也就是
                 h % 2^p
          返回的將是h的p個最低位組成的數字,我們假設hash輸入是符合簡單一致散列,然而這一假設并不能推論出hash的p個最低位也會符合簡單一致散列,也許h的這p個最低位相同的幾率很大,那么沖突的幾率就非常大了。優秀的散列函數應該需要考慮所有的位。

          因此為了防止這些“壞”的散列函數造成效率的降低,HashMap預先對hash值做了處理以考慮到所有的位,根據注釋也可以知道。這個處理我看不懂,留待高人解釋,也許來自于某本算法書也不一定。

          4、我們知道java.util.HashMap不是線程安全的,因此如果在使用迭代器的過程中有其他線程修改了map,那么將拋出ConcurrentModificationException,這就是所謂fail-fast策略(速錯),這一策略在源碼中的實現是通過 modCount域,modCount顧名思義就是修改次數,對HashMap內容的修改都將增加這個值,那么在迭代器初始化過程中會將這個值賦給迭代器的expectedModCount,

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

          在迭代過程中,判斷modCount跟expectedModCount是否相等,如果不相等就表示已經有其他線程修改了map

            
          final Entry<K,V> nextEntry() {
                      
          if (modCount != expectedModCount)
                          
          throw new ConcurrentModificationException();
           注意到modCount聲明為volatile,保證線程之間修改的可見性。
           




          主站蜘蛛池模板: 观塘区| 临城县| 富源县| 中方县| 驻马店市| 瑞丽市| 桂林市| 农安县| 桐梓县| 锡林郭勒盟| 玉门市| 安泽县| 腾冲县| 霸州市| 图们市| 慈溪市| 罗田县| 峨眉山市| 晋州市| 延庆县| 红桥区| 万安县| 宁波市| 林口县| 民勤县| 绵竹市| 淄博市| 张家港市| 广饶县| 历史| 翁源县| 崇礼县| 东安县| 阳高县| 随州市| 江孜县| 通道| 多伦县| 林州市| 宣汉县| 乐东|