HashMap中的哈希函数分析
首先我们要知道,在理想情况下的哈希表中,哈希函数生成的哈希值是value在数组中的下标,其范围是分布于负无穷到正无穷的整个实整数轴的。而在现实情况下,是不可能存在这么大的一个数组的。接下来分析HashMap怎么处理:
HashMap的put方法:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
put方法使用的不是Object提供的key.hashcode(),而是hash(key):
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
在key!=0的情况下,进行一下拆解分析:
static final int hash(Object key) {
int h = key.hashCode();
int l = h>>>16;
return h^l;
}
先取Object.hashcode(),是32位;然后右移16位,将低16位丢弃;将hashCode的低16位与高16位进行按位异或运算然后返回。
这就是扰动函数,扰动函数是如何减少冲突的?
由开头的分析,我们知道HashMap是不可能使用直接的哈希值的,因为不可能一个HashMap就要分配无限大(或者2^32次方大)的数组空间。
因此实际上HashMap是将哈希值对当前数组长度取余:
//源码部分截取
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
看tab[i = (n - 1) & hash]这里,HashMap在数组中的实际下标其实是 (数组长度-1)&hash,其实就是hash%数组长度。
以初始长度16为例,一个哈希值分布于整个实整数轴,取余16之后,必然分布于[0,15]区间范围内,也就无需去分配无限大的数组空间了。
这样做有什么问题呢?
一个好的哈希函数,要做到生成的哈希值足够分散。但是对数组长度取余后,相当于只截取了低位(因为HashMap的容量总是16的整数倍)。
如果一个key的哈希值的低四位是0010,那么在取余16之后,就只剩下0010,也就是十进制2。
哈希函数可能设计得在低位不是那么地随机,那么只保留低位的效果,就相当于完全抛弃了高位的随机性,因此需要这样的扰动函数,将高位与低位进行运算,增强低位的随机性。
在这篇文章中《An introduction to optimising a hashing strategy》,对比发现,采用高位扰动低位的方式进行hash,会使得哈希冲突减少10%。
顺便分析一下为什么HashMap的容量总是2的幂次方
首先HashMap的初始容量是16,随后每当实际容量占到了扩容因子*最大容量后,容量扩大为当前的两倍。因此HashMap的容量总是16*2的幂次方。
之前说得hashcode取余数组长度,只有在数组长度为2的幂次方的情况下,才可以转为(n - 1) & hash的位运算,从而提高运算效率。
HashMap中的哈希函数分析的更多相关文章
- HashMap 中的哈希值计算问题
date: 2020-08-21 16:48:00 updated: 2020-08-21 16:52:00 HashMap 中的哈希值计算问题 1. hash 计算 JDK1.8 HashMap源码 ...
- Java中String的hash函数分析
转载自:http://blog.csdn.net/hengyunabc/article/details/7198533 JDK6的源码: [java] view plaincopy /** * Ret ...
- openstack(liberty): devstack中的iniset/iniget函数分析
这个ini开头的函数在devstack的启动配置中用的非常多,他主要负责.ini文件的配置,这个过程包括对相关ini文件的添加,注释,删除,获取信息,多行信息获取等. 这里主要说的iniset和ini ...
- C++中的内联函数分析
1,本节课学习 C++ 中才引入的新的概念,内联函数: 2,常量与宏回顾: 1,C++ 中的 const 常量可以替代宏常数定义,如: 1,const int A = 3; <==> #d ...
- HashMap在并发下可能出现的问题分析
我们都知道,HashMap在并发环境下使用可能出现问题,但是具体表现,以及为什么出现并发问题,可能并不是所有人都了解,这篇文章记录一下HashMap在多线程环境下可能出现的问题以及如何避免. 在分析H ...
- HashMap的小总结 + 源码分析
一.HashMap的原理 所谓Map,就是关联数组,存的是键值对——key&value. 实现一个简单的Map,你也许会直接用两个LIst,一个存key,一个存value.然后做查询或者get ...
- EPANET中的哈希文件——hash.c
/*-----------------------------------------------------------------------------** hash.c**** Imp ...
- 【C# 集合】Hash哈希函数 |散列函数|摘要算法
希函数定义 哈希函数(英語:Hash function)又称散列函数.散列函数.摘要算法.单向散列函数.散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来.该函数将数据打乱混合,重新 ...
- 2、JDK8中的HashMap实现原理及源码分析
本篇提纲.png 本篇所述源码基于JDK1.8.0_121 在写上一篇线性表的文章的时候,笔者看的是Android源码中support24中的Java代码,当时发现这个ArrayList和Linked ...
随机推荐
- mmsegmentation中构造自己的数据集和数据加载部分,跑现有demo
在mmsegmentation中训练自己的数据集 先在mmse/dataset下创建一个python文件,我的名字是my_thermal_dataset.py 在其中填写下面内容 这里要注意,在设置s ...
- 关于transform属性的一些理解
3D transform transform进行动画演示时,是以元素的中心为基准点的,可以使用transform-origin改变元素转变的基准点. 所有的transform动作改变都会引起X.Y轴的 ...
- AQS源码三视-JUC系列
AQS源码三视-JUC系列 前两篇文章介绍了AQS的核心同步机制,使用CHL同步队列实现线程等待和唤醒,一个int值记录资源量.为上层各式各样的同步器实现画好了模版,像已经介绍到的ReentrantL ...
- 使用Spring MVC开发RESTful API(续)
使用多线程提高REST服务性能 异步处理REST服务,提高服务器吞吐量 使用Runnable异步处理Rest服务 AsyncController.java @RestController @GetMa ...
- 好客租房19-react组件基础目标
1能够使用函数创建组件 2能够使用class创建组件 3能够给react元素绑定事件 4能够使用state和setstate 5能够处理事件中的this指向问题 6能够使用受控组件处理表单
- .NET 6 从0到1使用Docker部署至Linux环境
前言 作为一名.Net菜鸟开发者,平时对Linux接触的并不多,项目部署这一块都是运维小哥顶着,但是作为混迹在云原生项目组的人咱也不能什么都不知道,该掌握的知识还是要懂的,所以借着这次机会,梳理一下项 ...
- vue项目|在弹窗中引入uchart图表子组件不显示
为了解决uchart作为子组件在主组件里引用但不显示的情况,(同样适用于弹窗之中)目前有三种方法. 1-解决方式 1>如果你使用的uchart子组件是从官方拿的例子:进入到uchart子组件将o ...
- Seata源码分析——SessionManager
目录 事务管理器 SessionManager SessionLifecycleListener AbstractSessionManager 事务存储管理器 RedisTransactionStor ...
- @vue/cli3+配置build命令构建测试包&正式包
上一篇博客介绍了vue-cli2.x配置build命令构建测试包和正式包,但现在前端开发vue项目大多数使用新版@vue/cli脚手架搭建vue项目(vue create project-name) ...
- ThreadLocal模板
public class UserIdThreadLocal { private static final ThreadLocal<Long>LOCAL=new ThreadLocal&l ...