1. 程式人生 > >HashMap底層原理小記

HashMap底層原理小記

return vat 閾值 ati 使用 end amp 序列化 表頭

  public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable

  首先HashMap 繼承自AbstractMap(抽象類) 實現了Map接口。

  在new HashMap<K, V>()時,通過底層代碼可以知道:

public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
}

  其中loadFactor為float型,指hash表的負載因子;DEFAULT_INITIAL_CAPACITY 為常量,是hash表的初始容量,初值為16;threshold為int型,是hash表的裝載閾值,反應了hash表的裝載程度。接下來就會初始化一個Entry類型的數組,初始容量為16;而其中table的類型Entry是Hashmap的一個靜態內部類。

static class Entry<K,V> implements Map.Entry<K,V>,並且該類沒有無參構造函數,
  Entry(int h, K k, V v, Entry<K,V> n) {	
            value = v;
            next = n;
            key = k;
            hash = h;
        }

 當使用hashMap.put(K,V)時, 

public V put(K key, V value) {
        if (key == null)				//判斷鍵是否為null
            return putForNullKey(value);	//添加鍵為null的值
        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) {	//定位到table中的位置後,取出當前的鏈表
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {	//判斷key是否重復
                V oldValue = e.value;					//重復的時候返回重復key的value值
                e.value = value;
                e.recordAccess(this);				//這句的作用是什麽不知道
                return oldValue;
            }
        }
        modCount++;				// modCount是不可被序列化的,具有可見性的,
//用來記錄hashmap的修改次數
        addEntry(hash, key, value, i);		//添加一個鍵值對
        return null;
}

  

void addEntry(int hash, K key, V value, int bucketIndex) {
	Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        if (size++ >= threshold)
//當hash表的大小>=閾值時進行擴容,hash表的大小變為原來的兩倍。
            resize(2 * table.length);
}

Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
//直接讓原來table[bucketIndex]位置上的表頭為新元素的next值,說明,每次插入元素都會在表位置中的表頭。
            next = n;
            key = k;
            hash = h;
        }

void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {//當hash表的容量到達最大值時不再擴容
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable);	//將原來hash表中的內容復制到新的hash表中
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }

  Hashmap中只允許有一個空鍵的原因:

  從代碼中可以看出,當第二次添加空鍵的時候,會將原來null所對應的值覆蓋,而且,空鍵所對應的key存放在表的第一個位置table[0].通過addEntry(0, null, value, 0),在第一次添加空鍵元素的時候,可以保證null所對應的元素在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;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }

 

HashMap底層原理小記