1. 程式人生 > >JDK原始碼-HashMap-put方法(JDK7和JDK8)

JDK原始碼-HashMap-put方法(JDK7和JDK8)

下面是對HashMap中put方法的原始碼進行註釋

測試程式碼

    /**
     * 測試put操作的區別
     */
    @Test
    public void put(){
        HashMap<String, String> map = new HashMap();
        map.put("aa","aa");
    }

JDK 7

public V put(K key, V value) {
	//校驗key是否為空
	if (key == null)
		return putForNullKey
(value); int hash = hash(key); //獲取key對應的hash值 int i = indexFor(hash, table.length); //得到該KV對應的table的index //這個for迴圈就是在校驗table[i]對應的連結串列中要插入的K key有沒有存在,如果有,那麼就用put的 value替換,然後返回該key對應的老的value 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))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++;//修改次數+1 addEntry(hash, key, value, i); //確定key沒有重複之後,插入(K,V) return null; }

點選 addEntry 方法

void addEntry(int hash, K key, V value, int bucketIndex) {
	//判斷是否需要擴容
if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); hash = (null != key) ? hash(key) : 0; //擴容後,對應的hash需要重新計算 bucketIndex = indexFor(hash, table.length);//擴容後,對應的bucketIndex需要重新計算 } //判讀是否需要擴容後,插入 createEntry(hash, key, value, bucketIndex); }

點選 createEntry 方法

void createEntry(int hash, K key, V value, int bucketIndex) {
	//採用頭插法插入(由於上面已經對key是否存在校驗過了,所以這裡保證了HashMap中要插入的key是不存在的,所以這裡採用頭插法是沒有問題的)
	Entry<K,V> e = table[bucketIndex];
	table[bucketIndex] = new Entry<>(hash, key, value, e);
	size++;
}

JDK 8

public V put(K key, V value) {
	return putVal(hash(key), key, value, false, true);
}

點選 putVal 方法

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
			   boolean evict) {
	/**
	 * Node<K,V>[] tab  指向HashMap中table的指標
	 * Node<K,V> p		table[i]指向的物件
	 * n				table的長度
	 * i				p在table對應的index
	 */
	Node<K,V>[] tab; Node<K,V> p; int n, i;
	//如果還沒初始化就初始化
	if ((tab = table) == null || (n = tab.length) == 0)
		n = (tab = resize()).length;
	//如果插入到bulkindex對應的table的slot還沒有值,那麼直接將(K,V)封裝的newNode物件賦給tab[i]
	if ((p = tab[i = (n - 1) & hash]) == null)
		tab[i] = newNode(hash, key, value, null);
	else {
		Node<K,V> e; K k;
		//如果插入的key跟table[i]的key相同,那麼將table[i]的slot的值(node)賦給e
		if (p.hash == hash &&
			((k = p.key) == key || (key != null && key.equals(k))))
			e = p;
		//否則,如果table[i]是紅黑樹型別,則呼叫紅黑樹的插入方法
		else if (p instanceof TreeNode)
			e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
		//否則,table[i]是連結串列
		else {
			for (int binCount = 0; ; ++binCount) {	
				//採用尾插法插入新的node
				if ((e = p.next) == null) { //注意一個很隱蔽的操作,e已經賦值為p.next了。開始遍歷連結串列,e就是臨時記錄node的指標
					p.next = newNode(hash, key, value, null);
					//如果連結串列的長度大於指定的長度,預設是8,則轉換成紅黑樹
					if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
						treeifyBin(tab, hash);
					break;
				}
				//如果連結串列中有存在跟插入的node的key和hash是一樣的話,那麼e指定該節點,然後退出迴圈
				if (e.hash == hash &&
					((k = e.key) == key || (key != null && key.equals(k))))
					break;
				p = e; //下個節點
			}
		}
		//說明有key相同的node
		if (e != null) {
			V oldValue = e.value;
			if (!onlyIfAbsent || oldValue == null)
				e.value = value;
			afterNodeAccess(e);
			return oldValue;
		}
	}
	++modCount;	//修改次數+1
	if (++size > threshold) //插入後判斷是否需要擴容
		resize();
	afterNodeInsertion(evict);//相容LinkedHashMap的方法,針對HashMap不用管
	return null;
}

總結

JDK 7 的插入操作是先判斷key是否已經存在,
如果存在,替換value,返回old value
如果不存在,頭插法插入

JDK8 的插入先判斷是否存在,
如果存在則替換value,用一個Node e指向old node,用來後續判斷
如果是不存在,直接在尾部插入該node
如果是紅黑樹,那麼呼叫紅黑樹的put方法