春招修仙系列 —— hashMap
参考:https://zhuanlan.zhihu.com/p/21673805 Java 8系列之重新认识HashMap — 美团技术团队,建议直接去看该文章,这里只是一些摘要
存储结构:
HashMap = 数组+ 链表+红黑树(JDK1.8)
关键变量:
/** 哈希表中的默认初始容量(table.length) **/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
/** 哈希表的最大容量 **/
static final int MAXIMUM_CAPACITY = 1 << 30;
/** 负载因子的默认值 **/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/** 将链表树化的阀值,即当链表存储量达到多大时,将其转化为树 **/
static final int TREEIFY_THRESHOLD = 8;
/** 将树转化为链表的阀值 **/
static final int UNTREEIFY_THRESHOLD = 6;
/** 这个字段决定了当hash表的至少大小为多少时,链表才能进行树化。**/
/**当存储的节点过多时,最好的办法是调整表的大小,使其增大,而不是将链表树化。 **/
static final int MIN_TREEIFY_CAPACITY = 64;
哈希冲突的解决策略
HashMap采用哈希表存储,解决冲突的策略是链地址法。
注:还有一些其他的解决冲突的办法,比如ThreadLoaclMap中使用的就是开发寻址法中的线性探测法
核心代码:
1 确定哈希桶数组索引的位置(方法一 + 方法二):
方法一:
static final int hash(Object key) { //jdk1.8 & jdk1.7
int h;
// h = key.hashCode() 为第一步 取hashCode值
// h ^ (h >>> 16) 为第二步 高位参与运算
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
方法二:
static int indexFor(int h, int length) { //jdk1.7的源码,jdk1.8没有这个方法,但是实现原理一样的
return h & (length-1); //第三步 取模运算
}
总结:取Key的hashCode值,高位运算、取模运算
分析:
- 用 h & (lengh - 1)来得到该对象得保存位,等价于取模运算。这也是为什么哈系桶table的长度大小必须是2的n次方的原因,避免产生0位,0位与任何数做&都为0,会使哈希表中的某些位置一直得不到使用。
- h^(h>>>16)的目的是让数据的高位和低位都能参与到Hash的计算中,使散列更加的均匀
2 HashMap中的put方法:
1 判断键值对数组table[i] 是否位空或为null,否则执行resize()进行扩容
2 根据键值Key计算hash值得到数组索引 I ,如果table[i] == null ,则直接添加节点,转向第6步,如果table[i]不为空,转到3步
3 判断table[i]的首个元素是否和key一样,如果相同则直接覆盖value,否则转向4,这里的相同指的是 hashCode 以及equals
4 判断table[i]是否是红黑树,如果是,则在树中直接插入数据,否则转向5
5 遍历table[i],判断链表长度是否大于8,大于的话就把链表转换为红黑树,在红黑树里执行插入操作,否则进行链表的插入操作,如果遍历的过程中,发现key已经存在了,直接覆盖value即可
6 插入成功后,该哈希表的复杂率是否大于负载因子,如果超过,进行扩容
3 扩容机制:
Jdk1.7:创建新的数组,哈希表中全部的值重新哈希
Jdk1.8 : 使用2次幂的扩展,所以元素的位置要么在原位置,要么在原位置再移动2次幂的位置,
元素再重新计算hash之后,由于n变为原来的2倍,那么n-1的mask范围再高位多1bit,因此新的index就会发生这样的变化
因此在扩充HashMap的时候,不需要像JDK1.7实现那样,重新计算hash,只要看看原来的hash值新增的那个bit是1还是0就可以了,是0的话,索引没变,是1的话索引变成了 原索引+ “oldCap”,这个设计十分的巧妙:
1 省去了重新计算hash值的时间
2 由于新增的1bit可以认为是随机的,所以它会均匀的将之前的冲突节点分散到新的bucket中
3 JDK1.7在 rehash的时候,旧得链表迁移到新表的时候,如果在新表中的数组索引位置相同,则会出现链表元素的倒置。
- 后短信集成时代
- jQuery仿极客公园火箭发射“返回顶部”效果(WordPress代码篇)
- Windows 2008 R2 Server Core .NET环境配置
- Request——Node世界中被依赖最多的库No.2
- 在传统.NET Framework 上运行ASP.NET Core项目
- .net core快速上手
- logicaldoc的外部认证——AD集成
- CLR 4.0 安全模型
- 应用工具 .NET Portability Analyzer 分析迁移dotnet core
- 使用无觅相关文章插件一定要删除的代码
- 管理混合云环境的5个要点
- Team Foundation Server 2010 – Basic Installation
- 富文本编辑器的一键排版功能
- 通过ProGet搭建一个内部的Nuget服务器
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 学习PHP弱引用的知识
- 「okhttp」Gradle引用改jar包引用(一波三折)
- 「问答」解决CSV文件用Excel打开乱码问题
- 「问答」解决jar包运行时相对路径问题
- 「Eclipse」生成能用命令行运行的jar包
- 「AndroidStudio」fastjson导包报错:Could not resolve com.alibaba:fastjson:1.1.56.android
- 「Android」通过注解自动生成类文件:APT实战(AbstractProcessor)
- 五、开始Github和码云之旅,新手如何上路
- 用 Shader 写个完美的波浪~
- K8s上的Go服务怎么扩容、发版更新、回滚、平滑重启?教你用Deployment全搞定!
- 图解Go内存管理器的内存分配策略
- why哥这里有一道Dubbo高频面试题,请查收。
- 「容器平台」Kubernetes网络策略101
- 架构师之路 - 服务器硬件扫盲
- 零基础Python教程045期 元组的增删改查测试实验