1. 程式人生 > >HashMap原理分析

HashMap原理分析

HashMap基於雜湊表的Map介面實現,其中每個元素是一個key-value對,允許使用null值和null鍵。內部使用單鏈表的方式解決hash衝突的問題,當容量不足時,它會自動增長容量。

HashMap中的部分欄位:

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//預設初始容量為16
static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量為2的30次方
static final float DEFAULT_LOAD_FACTOR = 0.75f;//預設載入因子為0.75
transient Entry[] table;//入口陣列,每個Entry儲存一條連結串列的頭結點
transient int size;//入口陣列中已用槽的數量
transient int modCount;//被修改的次數
int threshold;//容量閥值,用於判斷是否需要調整HashMap的容量(threshold = 容量 * 載入因子)
final float loadFactor;//載入因子實際大小

HashMap的幾種建構函式:

    /*指定初始容量initialCapacity和載入因子loadFactor的建構函式*/
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;//最大初始容量不允許超過2的30次方
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        //初始化欄位
    }
    /*指定初始容量initialCapacity的建構函式*/
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    /*預設建構函式*/
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
    }
    /*傳入一個Map作為引數的建構函式*/
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

Entry入口陣列:

Entry數組裡的每個元素儲存一個key-value對,當前key的hash值、以及指向下一個key-value對的指標next。

static class Entry<K,V> implements Map.Entry<K,V> {  
       final K key;  
       V value;  
       final int hash;  
       Entry<K,V> next;  
   
       Entry(int h, K k, V v, Entry<K,V> n) {  
           value = v;  
           next = n;  
           key = k;  
           hash = h;  
       }  
}
HashMap中的get操作:
public V get(Object key) {    
    if (key == null) //如果key == null,進入table[0]所指示的連結串列處查詢 
        return getForNullKey();    

    int hash = hash(key.hashCode()); //獲取key的hash值   

    for (Entry<K,V> e = table[indexFor(hash, table.length)]; // 遍歷指定連結串列找出value值
         e != null;    
         e = e.next) {    
        Object k;    

        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))    
            return e.value;    
    }  
    //找不到值返回null
    return null;
}
/*key為null的元素儲存在入口陣列的第一個位置(即table[0])處所指示的連結串列中*/   
private V getForNullKey() {    
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {    
        if (e.key == null)    
            return e.value;    
    }    
    return null;    
}  
/*將hash值與入口陣列的長度進行某種形式上的取模運算,返回指定下標*/
static int indexFor(int h, int length) {
        return h & (length-1);
    }
get方法的步驟:


程式碼化為:

if key == null
	Entry<K, V> head = table[0];
	for Each node in list
		if found
			return value;
	end for 
	return null;

if key != null
	int hash = key.hashCode();
	int i = hash % table.length;
	Entry<K, V> head= table[i];
	for Each node in list
		if found
			return value;
	end for  
	return null;

HashMap中的put操作:

public V put(K key, V value) {    
      if (key == null)    //若key為null,則將key-value對加入到table[0]處
          return putForNullKey(value);    
       
      int hash = hash(key.hashCode()); //獲取key的hash值
      int i = indexFor(hash, table.length);  //將hash值與入口陣列的長度進行取模運算返回指定位置下標
      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值對應的key-value對存在,則用新value值替換掉舊value值  
              V oldValue = e.value;    
              e.value = value;    
              e.recordAccess(this);    
              return oldValue;    
          }    
      }    
  
      // 若key值對應的key-value對不存在,則將此key-value對儲存至入口陣列中  
      modCount++;  
      // 將key-value對新增到table[i]處  
      addEntry(hash, key, value, i);    
      return null;    
  } 
/*putForNullKey()的作用是將key為null的key-value對新增到table[0]位置*/    
private V putForNullKey(V value) {    
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {    
        if (e.key == null) {    
            V oldValue = e.value;    
            e.value = value;    
            e.recordAccess(this);    
            return oldValue;    
        }    
    }    
    // 如果沒有存在key為null的鍵值對,則直接新增到table[0]處 
    modCount++;    
    addEntry(0, null, value, 0);    
    return null;    
}   
/*addEntry()的作用是新增一個Entry元素。將key-value對插入指定位置,bucketIndex是位置索引*/    
void addEntry(int hash, K key, V value, int bucketIndex) {    
    // 頭插法插入新Entry元素   
    Entry<K,V> e = table[bucketIndex];        
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);    
    // 若HashMap的實際大小大於或等於閥值,則重新調整HashMap的大小    
    if (size++ >= threshold)    
        resize(2 * table.length);    
}    
/*resize()的作用是重新調整HashMap的大小,newCapacity是調整後的單位*/    
void resize(int newCapacity) {    
    Entry[] oldTable = table;    
    int oldCapacity = oldTable.length;    
    if (oldCapacity == MAXIMUM_CAPACITY) {    
        threshold = Integer.MAX_VALUE;    
        return;    
    }    
  
    // 新建一個HashMap,將"舊HashMap"的全部元素新增到"新HashMap"中,    
    // 然後,將"新HashMap"賦值給"舊HashMap"。    
    Entry[] newTable = new Entry[newCapacity];    
    transfer(newTable);    
    table = newTable;    
    threshold = (int)(newCapacity * loadFactor);    
}  
/*將HashMap中的全部元素都轉移到newTable中*/    
void transfer(Entry[] newTable) {    
    Entry[] src = table;    
    int newCapacity = newTable.length;    
    for (int j = 0; j < src.length; j++) {    
        Entry<K,V> e = src[j];    
        if (e != null) {    
            src[j] = null;    
            do {    
                Entry<K,V> next = e.next;    
                int i = indexFor(e.hash, newCapacity);    
                e.next = newTable[i];    
                newTable[i] = e;    
                e = next;    
            } while (e != null);    
        }    
    }    
}    
put方法的步驟: