從源碼層理解Hashtable中的put和get

 首先我們先看put方法:將指定 key 映射到此哈希表中的指定 value。注意這裏鍵key和值value都不可爲空。

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public synchronized V put(K key, V value) {  
  2.         // 確保value不爲null  
  3.         if (value == null) {  
  4.             throw new NullPointerException();  
  5.         }  
  6.   
  7.         /* 
  8.          * 確保key在table[]是不重複的 
  9.          * 處理過程: 
  10.          * 1、計算key的hash值,確認在table[]中的索引位置 
  11.          * 2、迭代index索引位置,如果該位置處的鏈表中存在一個一樣的key,則替換其value,返回舊值 
  12.          */  
  13.         Entry tab[] = table;  
  14.         int hash = hash(key);    //計算key的hash值  
  15.         int index = (hash & 0x7FFFFFFF) % tab.length;     //確認該key的索引位置  
  16.         //迭代,尋找該key,替換  
  17.         for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {  
  18.             if ((e.hash == hash) && e.key.equals(key)) {  
  19.                 V old = e.value;  
  20.                 e.value = value;  
  21.                 return old;  
  22.             }  
  23.         }  
  24.   
  25.         modCount++;  
  26.         if (count >= threshold) {  //如果容器中的元素數量已經達到閥值,則進行擴容操作  
  27.             rehash();  
  28.             tab = table;  
  29.             hash = hash(key);  
  30.             index = (hash & 0x7FFFFFFF) % tab.length;  
  31.         }  
  32.   
  33.         // 在索引位置處插入一個新的節點  
  34.         Entry<K,V> e = tab[index];  
  35.         tab[index] = new Entry<>(hash, key, value, e);  
  36.         //容器中元素+1  
  37.         count++;  
  38.         return null;  
  39.     }  

        put方法的整個處理流程是:計算key的hash值,根據hash值獲得key在table數組中的索引位置,然後迭代該key處的Entry鏈表(我們暫且理解爲鏈表),若該鏈表中存在一個這個的key對象,那麼就直接替換其value值即可,否則在將改key-value節點插入該index索引位置處。如下:

        首先我們假設一個容量爲5的table,存在8、10、13、16、17、21。他們在table中位置如下:


        然後我們插入一個數put(16,22),key=16在table的索引位置爲1,同時在1索引位置有兩個數,程序對該“鏈表”進行迭代,發現存在一個key=16,這時要做的工作就是用newValue=22替換oldValue16,並將oldValue=16返回。


        在put(33,33),key=33所在的索引位置爲3,並且在該鏈表中也沒有存在某個key=33的節點,所以就將該節點插入該鏈表的第一個位置。


        在HashTabled的put方法中有兩個地方需要注意:

        1、HashTable的擴容操作,在put方法中,如果需要向table[]中添加Entry元素,會首先進行容量校驗,如果容量已經達到了閥值,HashTable就會進行擴容處理rehash(),如下:

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. protected void rehash() {  
  2.         int oldCapacity = table.length;  
  3.         //元素  
  4.         Entry<K,V>[] oldMap = table;  
  5.   
  6.         //新容量=舊容量 * 2 + 1  
  7.         int newCapacity = (oldCapacity << 1) + 1;  
  8.         if (newCapacity - MAX_ARRAY_SIZE > 0) {  
  9.             if (oldCapacity == MAX_ARRAY_SIZE)  
  10.                 return;  
  11.             newCapacity = MAX_ARRAY_SIZE;  
  12.         }  
  13.           
  14.         //新建一個size = newCapacity 的HashTable  
  15.         Entry<K,V>[] newMap = new Entry[];  
  16.   
  17.         modCount++;  
  18.         //重新計算閥值  
  19.         threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);  
  20.         //重新計算hashSeed  
  21.         boolean rehash = initHashSeedAsNeeded(newCapacity);  
  22.   
  23.         table = newMap;  
  24.         //將原來的元素拷貝到新的HashTable中  
  25.         for (int i = oldCapacity ; i-- > 0 ;) {  
  26.             for (Entry<K,V> old = oldMap[i] ; old != null ; ) {  
  27.                 Entry<K,V> e = old;  
  28.                 old = old.next;  
  29.   
  30.                 if (rehash) {  
  31.                     e.hash = hash(e.key);  
  32.                 }  
  33.                 int index = (e.hash & 0x7FFFFFFF) % newCapacity;  
  34.                 e.next = newMap[index];  
  35.                 newMap[index] = e;  
  36.             }  
  37.         }  
  38.     }  

        在這個rehash()方法中我們可以看到容量擴大兩倍+1,同時需要將原來HashTable中的元素一一複製到新的HashTable中,這個過程是比較消耗時間的,同時還需要重新計算hashSeed的,畢竟容量已經變了。這裏對閥值囉嗦一下:比如初始值11、加載因子默認0.75,那麼這個時候閥值threshold=8,當容器中的元素達到8時,HashTable進行一次擴容操作,容量 = 11* 2 + 1 =23,而閥值threshold=23*0.75 = 17,當容器元素再一次達到閥值時,HashTable還會進行擴容操作,一次類推。

     2、在計算索引位置index時,HashTable進行了一個與運算過程(hash & 0x7FFFFFFF)下面是計算key的hash值,這裏hashSeed發揮了作用。

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. private int hash(Object k) {  
  2.         return hashSeed ^ k.hashCode();  
  3.     }  

        相對於put方法,get方法就會比較簡單,處理過程就是計算key的hash值,判斷在table數組中的索引位置,然後迭代鏈表,匹配直到找到相對應key的value,若沒有找到返回null。

[java] view plain copy
 print?在CODE上查看代碼片派生到我的代碼片
  1. public synchronized V get(Object key) {  
  2.         Entry tab[] = table;  
  3.         int hash = hash(key);  
  4.         int index = (hash & 0x7FFFFFFF) % tab.length;  
  5.         for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {  
  6.             if ((e.hash == hash) && e.key.equals(key)) {  
  7.                 return e.value;  
  8.             }  
  9.         }  
  10.         return null;  
  11.     }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章