JDK源码-HashMap-put方法(JDK7和JDK8)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Lin_wj1995/article/details/83893901

下面是对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方法

猜你喜欢

转载自blog.csdn.net/Lin_wj1995/article/details/83893901