-
HashSet的add()方法源码解析(jdk1.8)
- 实现了Set接口
- 实际上是HashMap
- 可以存null,但只能有一个
- 不保证元素是有序的,取决于hash后,在确定索引结果
add源码
//核心操作putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 步骤①:tab为空则创建
// table未初始化或者长度为0,进行扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 步骤②:计算index,并对null做处理
// (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数组中)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 桶中已经存在元素
else {
Node<K,V> e; K k;
// 步骤③:节点key存在,直接覆盖value
// 比较桶中第一个元素(数组中的结点)的hash值相等,key相等
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// 将第一个元素赋值给e,用e来记录
e = p;
// 步骤④:判断该链为红黑树
// hash值不相等,即key不相等;为红黑树结点
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);
// 结点数量达到阈值,转化为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
// 跳出循环
break;
}
// 判断链表中结点的key值与插入的元素的key值是否相等
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
// 相等,跳出循环
break;
// 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表
p = e;
}
}
// 表示在桶中找到key值、hash值与插入元素相等的结点
if (e != null) {
// 记录e的value
V oldValue = e.value;
// onlyIfAbsent为false或者旧值为null
if (!onlyIfAbsent || oldValue == null)
//用新值替换旧值
e.value = value;
// 访问后回调
afterNodeAccess(e);
// 返回旧值
return oldValue;
}
}
// 结构性修改
++modCount;
// 步骤⑥:超过最大容量 就扩容
// 实际大小大于阈值则扩容
if (++size > threshold)
resize();
// 插入后回调
afterNodeInsertion(evict);
return null;
}
解释:add流程
-
使用构造器时,执行新建一个HashMap对象
-
执行add方法
-
执行map的put方法
- 计算出hash值为:key.hash = (h = k.hashCode()) ^ (h >>> 16);(hashCode与自身无符号右移16位做异或)
因为通常声明map集合时不会指定大小,或者初始化的时候就创建一个容量很大的map对象,所以这个通过容量大小与key值进行hash的算法在开始的时候只会对低位进行计算,虽然容量的2进制高位一开始都是0,但是key的2进制高位通常是有值的,因此先在hash方法中将key的hashCode右移16位在与自身异或,使得高位也可以参与hash,更大程度上减少了碰撞率。
-
执行putVal方法、
-
判断table是否为null(为null则扩容到16,阈值为0.75*容量 = 12)
-
使用hash进行高效取余计算出应该存在table表中的那个索引位置
- 索引位为null,直接存入时,新建一个Node对象,传入三个参数,hash值(为了下次添加时比较hash值),key值(添加的值),value值(一个哨兵变量,占位用,为了set使用hashMap,每个key的value都一样(PRESENT)),next(null)
-
不为null,产生冲突
- 判断是否属于同一个对象,或者equals判断相等(将e赋值为当前下标对应的Node)
- 判读是否属于红黑树,(属于则 将p强转为TreeNode,调用putTreeVal,将e赋值)
-
将当前下表的链表进行for循环
- 如果链表中有节点是和将要添加的对象属于同一对象,或者equals判断相等,则break;
-
如果循环到了链表尾,则进行添加
-
判断结点数量是否达到阈值(8),到8则转化为红黑树
- 转换之前,判断table数组大小是否小于64或等于null,小于则扩容
- 转化红黑树
-
判断结点数量是否达到阈值(8),到8则转化为红黑树
- 判断e是否为null,不为空返回旧值(添加失败)
-
使用hash进行高效取余计算出应该存在table表中的那个索引位置
- 判断++size是否大于阈值,大于则进行扩容
- 返回null(添加成功)
-
判断table是否为null(为null则扩容到16,阈值为0.75*容量 = 12)
__EOF
l
最新更新
python爬虫及其可视化
使用python爬取豆瓣电影短评评论内容
nodejs爬虫
Python正则表达式完全指南
爬取豆瓣Top250图书数据
shp 地图文件批量添加字段
爬虫小试牛刀(爬取学校通知公告)
【python基础】函数-初识函数
【python基础】函数-返回值
HTTP请求:requests模块基础使用必知必会
SQL SERVER中递归
2个场景实例讲解GaussDB(DWS)基表统计信息估
常用的 SQL Server 关键字及其含义
动手分析SQL Server中的事务中使用的锁
openGauss内核分析:SQL by pass & 经典执行
一招教你如何高效批量导入与更新数据
天天写SQL,这些神奇的特性你知道吗?
openGauss内核分析:执行计划生成
[IM002]Navicat ODBC驱动器管理器 未发现数据
初入Sql Server 之 存储过程的简单使用
uniapp/H5 获取手机桌面壁纸 (静态壁纸)
[前端] DNS解析与优化
为什么在js中需要添加addEventListener()?
JS模块化系统
js通过Object.defineProperty() 定义和控制对象
这是目前我见过最好的跨域解决方案!
减少回流与重绘
减少回流与重绘
如何使用KrpanoToolJS在浏览器切图
performance.now() 与 Date.now() 对比