HashMap工作原理

MartinaHaus 8年前發布 | 6K 次閱讀 鏈表 Java開發

相信大家不管是在Java還是安卓面試過程中,或多或少都會被問及HashMap的工作原理,小編今天大概看了一下Android中HashMap的源碼,將結果整理如下,如有不對之處請批評指正:

一、HashMap的數據結構

其實HashMap的存儲數據結構是一個散列數組+鏈表的數據結構,如圖:

HashMap的存儲數據結構

我們都知道往HashMap里存儲值時會傳入key和value,HashMap首先會拿到key相對應的hash值,

接著通過hash值計算存放數組的下標,再將value值存放在數組對應下標下的鏈表里。

接下來我們就根據源碼看看HashMap的存取實現。

二、HashMap的存取實現

1) put(key,value)

@Override 
public V put(K key, V value) {    
    if (key == null) {        
        return putValueForNullKey(value);    
    }    

    int hash = Collections.secondaryHash(key);    
    HashMapEntry<K, V>[] tab = table;    
    int index = hash & (tab.length - 1);    
    for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {        
         if (e.hash == hash && key.equals(e.key)) {            
             preModify(e);            
             V oldValue = e.value;            
             e.value = value;            
             return oldValue;        
         }    
     }    

     // No entry for (non-null) key is present; create one    
     modCount++;    
     if (size++ > threshold) {        
         tab = doubleCapacity();        
         index = hash & (tab.length - 1);    
     }    
     addNewEntry(key, value, hash, index);    
     return null;
}

由源碼可以看出,程序首先會檢測key值是否為空,如果為空則做空值處理(null key總是存放在entryForNullKey對象中);接著對key的hashCode()做hash,然后再計算index;接著在Entry[index]下的鏈表里查找是否存在hash和key值與插入key的一樣的HashMapEntry對象,如果有的話,則將舊值替換成value,并返回舊值;否則通過addNewEntry插入新的HashMapEntry對象,源碼如下:

void addNewEntry(K key, V value, int hash, int index) {    
    table[index] = new HashMapEntry<K, V>(key, value, hash, table[index]);
}
HashMapEntry(K key, V value, int hash, HashMapEntry<K, V> next) {    
    this.key = key;    
    this.value = value;    
    this.hash = hash;    
    this.next = next;
}

由方法可知,插入方法是將新的HashMapEntry對象當成table[index]下鏈表的頭結點,而用新的HashMapEntry對象的next指向原table[index]下鏈表的頭結點,以達成插入鏈表頭結點的目的。

2) get(key)

public V get(Object key) {    
    if (key == null) {        
        HashMapEntry<K, V> e = entryForNullKey;        
        return e == null ? null : e.value;    
    }    

    int hash = Collections.secondaryHash(key);    
    HashMapEntry<K, V>[] tab = table;    
    for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
          e != null; e = e.next) {        
          K eKey = e.key;        
          if (eKey == key || (e.hash == hash && key.equals(eKey))) {            
              return e.value;        
          }    
     }    
     return null;
}

由源碼可以看出,程序首先會判斷key值是否為空,如果為空,則檢查entryForNullKey有沒有存放值,有的話則返回;接著對key的hashCode()做hash,然后再計算index;接著在Entry[index]下的鏈表里查找是否存在hash和key值與插入key的一樣的HashMapEntry對象,有的話則返回。

三、HashMap的大小問題

  • 如果沒有設置初始大小,即直接new HashMap(),size為MINIMUM_CAPACITY 的二分之一
    /** * An empty table shared by all zero-capacity maps (typically from default
    * constructor). It is never written to, and replaced on first put. Its size
    * is set to half the minimum, so that the first resize will create a 
    * minimum-sized table. 
    */
    private static final Entry[] EMPTY_TABLE        
        = new HashMapEntry[MINIMUM_CAPACITY >>> 1];
    public HashMap() {    
        table = (HashMapEntry<K, V>[]) EMPTY_TABLE;    
        threshold = -1; // Forces first put invocation to replace EMPTY_TABLE
    }
  • 如果有設置初始大小,即調用new HashMap(capacity), 注意table初始大小并不是構造函數中的initialCapacity!!而是 >= initialCapacity并且是2的n次冪的整數!!!!

    public HashMap(int capacity) {    
       if (capacity < 0) {        
           throw new IllegalArgumentException("Capacity: " + capacity);    
       }    
    
       if (capacity == 0) {        
           @SuppressWarnings("unchecked")        
           HashMapEntry<K, V>[] tab = (HashMapEntry<K, V>[]) EMPTY_TABLE;        
           table = tab;        
           threshold = -1; // Forces first put() to replace EMPTY_TABLE        
           return;    
       }    
    
       if (capacity < MINIMUM_CAPACITY) {        
           capacity = MINIMUM_CAPACITY;    
       } else if (capacity > MAXIMUM_CAPACITY) {        
           capacity = MAXIMUM_CAPACITY;    
       } else {        
           capacity = Collections.roundUpToPowerOfTwo(capacity);    
       }    
       makeTable(capacity);
    }

    其中Collections的roundUpToPowerOfTwo方法,就是獲取大于等于 某個整數 并且是 2 的冪數的整數

  • 再散列 rehash :當哈希表的容量超過默認容量時,doubleCapacity會調整table的為原來的2倍。這時,需要創建一張新表,將原表的映射到新表中。
    private HashMapEntry<K, V>[] doubleCapacity() {    
        HashMapEntry<K, V>[] oldTable = table;    
        int oldCapacity = oldTable.length;    
        if (oldCapacity == MAXIMUM_CAPACITY) {        
            return oldTable;    
        }    
        int newCapacity = oldCapacity * 2;    
        HashMapEntry<K, V>[] newTable = makeTable(newCapacity);    
        if (size == 0) {        
            return newTable;    
        }    
        for (int j = 0; j < oldCapacity; j++) {       
             /*         
             * Rehash the bucket using the minimum number of field writes.         
             * This is the most subtle and delicate code in the class.         
             */        
             HashMapEntry<K, V> e = oldTable[j];        
             if (e == null) {            
                 continue;        
             }        
             int highBit = e.hash & oldCapacity;        
             HashMapEntry<K, V> broken = null;        
             newTable[j | highBit] = e;        
             for (HashMapEntry<K, V> n = e.next; n != null; e = n, n = n.next) {            
                  int nextHighBit = n.hash & oldCapacity;            
                  if (nextHighBit != highBit) {                
                      if (broken == null)                    
                          newTable[j | nextHighBit] = n;                
                      else                    
                          broken.next = n;                
                          broken = e;                
                          highBit = nextHighBit;            
                   }        
             }        
             if (broken != null)            
             broken.next = null;    
         }    
         return newTable;
    }

 

來自:http://www.jianshu.com/p/142aac8232e2

 

 本文由用戶 MartinaHaus 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!