IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 晦涩难懂的hashmap源代码-put方法解析 -> 正文阅读

[数据结构与算法]晦涩难懂的hashmap源代码-put方法解析

晦涩难懂的hashmap源代码-put方法解析


在我的分类专栏’hash’中,有关于位运算、hash计算、红黑树的文章,
如果这些还不太了解,可以去看看: hash

jdk版本:jdk1.8.0_171。

hashmap是用于key-value键值对处理的数据类型。

底层数组

hashmap底层是一个数组,
数组的特点是随机访问的效率高。
在这里插入图片描述
hashmap通过散列函数将key转换为索引,即数组下标,
来确定元素在数组中的存放位置。
在这里插入图片描述

hash冲突

散列函数计算得到的hash值,可能会存在重复的情况,
这个时候该怎么办呢?

链表节点

什么是链表节点?就是下图这样子:
在这里插入图片描述
hashmap是一个数组,数组中存放的是链表节点node,也叫桶即bucket。

put的时候,
会对key进行hash计算得到一个index,再根据index确定数组中的位置,
如果位置上已经有了元素,即hash冲突的时候,
就插入到该链表尾部节点的next引用上。

在这里插入图片描述

红黑树

如果在某个位置上的链表的长度超过了7,
会转换为红黑树。

太长会影响查询效率。
在这里插入图片描述

计算数组下标

如何根据key计算数组下标?

1、
得到扰动后的key的hashcode,h:
(h = key.hashCode()) ^ (h >>> 16);
将hashcode与hashcode>>>16进行异或运算,
hashcode>>>16是为了将高低位的特征混合,实现更均匀的散列,减少碰撞的几率。

/**
 * Computes key.hashCode() and spreads (XORs) higher bits of hash
 * to lower.  Because the table uses power-of-two masking, sets of
 * hashes that vary only in bits above the current mask will
 * always collide. (Among known examples are sets of Float keys
 * holding consecutive whole numbers in small tables.)  So we
 * apply a transform that spreads the impact of higher bits
 * downward. There is a tradeoff between speed, utility, and
 * quality of bit-spreading. Because many common sets of hashes
 * are already reasonably distributed (so don't benefit from
 * spreading), and because we use trees to handle large sets of
 * collisions in bins, we just XOR some shifted bits in the
 * cheapest possible way to reduce systematic lossage, as well as
 * to incorporate impact of the highest bits that would otherwise
 * never be used in index calculations because of table bounds.
 */
static final int hash(Object key) {
	int h;
	return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

2、
取模:
i = (n - 1) & hash
n是hashmap底层数组的长度,hash是上一步得到的h,
((n - 1) & hash) = (hash % (n - 1)),前提需要n=2的x次方,
用位移运算是因为效率更高,
这也是为什么hashmap的容量必须得是2的幂。

/**
 * The default initial capacity - MUST be a power of two.
 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

put源代码

都在注释里。

put

/**
 * Associates the specified value with the specified key in this map.
 * If the map previously contained a mapping for the key, the old
 * value is replaced.
 *
 * @param key key with which the specified value is to be associated
 * @param value value to be associated with the specified key
 * @return the previous value associated with <tt>key</tt>, or
 *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
 *         (A <tt>null</tt> return can also indicate that the map
 *         previously associated <tt>null</tt> with <tt>key</tt>.)
 */
public V put(K key, V value) {
	return putVal(hash(key), key, value, false, true);
}

返回null或被覆盖的value

/**
 * Implements Map.put and related methods
 *
 * @param hash hash for key
 * @param key the key
 * @param value the value to put
 * @param onlyIfAbsent if true, don't change existing value
 * @param evict if false, the table is in creation mode.
 * @return previous value, or null if none
 */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
			   boolean evict) {
	Node<K,V>[] tab; Node<K,V> p; int n, i;
	// table如果为null、为空,则调用resize()扩容,这个table就是hashmap的底层数组。
	if ((tab = table) == null || (n = tab.length) == 0)
		n = (tab = resize()).length;
	// 根据key计算得到的数组下标位置上没有元素,通过newNode创建一个Node放在这个位置上。
	// hash是(h = key.hashCode()) ^ (h >>> 16)扰动后的hashcode,(n - 1) & hash]是取模。
	if ((p = tab[i = (n - 1) & hash]) == null)
		tab[i] = newNode(hash, key, value, null);
	// 下标上存在元素
	else {
		// e:旧元素
		Node<K,V> e; K k;
		// 旧元素hash等于新元素hash,且旧key等于新key。
		if (p.hash == hash &&
			((k = p.key) == key || (key != null && key.equals(k))))
			// 准备覆盖旧元素
			e = p;
		// 旧元素是树节点,添加树节点元素。
		else if (p instanceof TreeNode)
			e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
		// 旧元素是链表节点
		else {
			for (int binCount = 0; ; ++binCount) {
				if ((e = p.next) == null) {
					// 将新元素插入到链表尾部
					p.next = newNode(hash, key, value, null);
					// 链表长度超过了7,转换为红黑树。
					if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
						treeifyBin(tab, hash);
					break;
				}
				if (e.hash == hash &&
					((k = e.key) == key || (key != null && key.equals(k))))
					// 准备覆盖旧元素
					break;
				// 遍历链表下一个节点
				p = e;
			}
		}
		// map中key重复
		if (e != null) { // existing mapping for key
			V oldValue = e.value;
			// onlyIfAbsent传进来的是false,意思是允许覆盖旧值或旧值为null。
			if (!onlyIfAbsent || oldValue == null)
				// 覆盖旧元素
				e.value = value;
			// hashmap的实现这一步什么都没有做
			afterNodeAccess(e);
			return oldValue;
		}
	}
	++modCount;
	// 是否要扩容
	if (++size > threshold) // threshold = 数组长度 * 负载因子(0.75)
		resize();
	// hashmap的实现,这一步什么都没有做。
	afterNodeInsertion(evict);
	return null;
}

Refs:
冰棍hfv:我用两万字详细总结HashMap(JDK1.8)底层原理
Code_BinBin:JDK1.8HashMap底层实现原理
码农小白猿:通俗易懂Hashmap源码解析

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-09-04 01:36:44  更:2022-09-04 01:38:55 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 19:21:30-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码