HashMap工作原理

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

一、HashMap的數(shù)據(jù)結(jié)構(gòu)

其實(shí)HashMap的存儲數(shù)據(jù)結(jié)構(gòu)是一個(gè)散列數(shù)組+鏈表的數(shù)據(jù)結(jié)構(gòu),如圖:

HashMap的存儲數(shù)據(jù)結(jié)構(gòu)

我們都知道往HashMap里存儲值時(shí)會(huì)傳入key和value,HashMap首先會(huì)拿到key相對應(yīng)的hash值,
接著通過hash值計(jì)算存放數(shù)組的下標(biāo),再將key-value對象存放在數(shù)組對應(yīng)下標(biāo)下的鏈表里。

接下來我們就根據(jù)源碼看看HashMap的存取實(shí)現(xiàn)。

二、HashMap的存取實(shí)現(xiàn)

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;
}

由源碼可以看出,程序首先會(huì)檢測key值是否為空,如果為空則做空值處理(null key總是存放在entryForNullKey對象中);接著對key的hashCode()做hash,然后再計(jì)算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對象當(dāng)成table[index]下鏈表的頭結(jié)點(diǎn),而用新的HashMapEntry對象的next指向原table[index]下鏈表的頭結(jié)點(diǎn),以達(dá)成插入鏈表頭結(jié)點(diǎn)的目的。

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;
}

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

所以,根據(jù)具體來說,HashMap的存儲結(jié)構(gòu)是這樣的:

HashMap的存儲結(jié)構(gòu)

三、HashMap的大小問題

  • 如果沒有設(shè)置初始大小,即直接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
}
  • 如果有設(shè)置初始大小,即調(diào)用new HashMap(capacity),注意table初始大小并不是構(gòu)造函數(shù)中的initialCapacity!!而是 >= initialCapacity并且是2的n次冪的整數(shù)!!!!
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方法,就是獲取大于等于 某個(gè)整數(shù) 并且是 2 的冪數(shù)的整數(shù)

  • 再散列rehash:當(dāng)哈希表的容量超過默認(rèn)容量時(shí),doubleCapacity會(huì)調(diào)整table的為原來的2倍。這時(shí),需要?jiǎng)?chuàng)建一張新表,將原表的映射到新表中。
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;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 1.概述 學(xué)習(xí)本文你可以了解到: HashMap 是什么樣的內(nèi)部結(jié)構(gòu)?有什么特點(diǎn)? 他的工作原理是什么樣? equ...
    忽忽_閱讀 407評論 0 0
  • 很多剛學(xué)Java的同學(xué)們都知道HashMap,平常一般使用,可能并不知道它的工作原理,前段時(shí)間有為剛畢業(yè)的同事在使...
    笑傲碼湖閱讀 8,291評論 6 12
  • 最近參與公司的實(shí)習(xí)生招聘工作,面試了幾位實(shí)習(xí)生,我有一道每次面試都必問的題目【HasmMap的工作原理】,但很遺憾...
    竹子柳閱讀 2,305評論 0 4
  • 大部分Java開發(fā)者都在使用Map,特別是HashMap。HashMap是一種簡單但強(qiáng)大的方式去存儲和獲取數(shù)據(jù)。但...
    騷的掉渣閱讀 811評論 0 14
  • Hash table based implementation of the Map interface. Thi...
    Johnsonxu閱讀 472評論 0 0