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 ...
随机推荐
- LintCode-165 · 合并两个排序链表-题解
描述:将两个排序(升序)链表合并为一个新的升序排序链表样例 1:输入: list1 = null, list2 = 0->3->3->null输出: 0->3->3-&g ...
- 团队Arpha5
队名:观光队 组长博客 作业博客 组员实践情况 王耀鑫 **过去两天完成了哪些任务 ** 文字/口头描述 完成服务器连接数据库部分代码 展示GitHub当日代码/文档签入记录 接下来的计划 服务器网络 ...
- 2020 最烂密码 TOP 200 大曝光!
点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 整理 | 王晓曼 出品 | 程序人生 (ID:coder ...
- RabbitMQ从概念到使用、从Docker安装到RabbitMQ整合Springboot【1.5w字保姆级教学】
@ 目录 一.前言 二.RabbitMQ作用 1. 异步处理 2. 应用解耦 3. 流量控制 三.RabbitMQ概念 1. RabbitMQ简介 2. 核心概念 四.JMS与AMQP比较 五.Rab ...
- vue - Vue路由(扩展)
忙里偷闲,还在学校,趁机把后面的路由多出来的知识点学完 十.缓存路由组件 让不展示的路由组件保持挂载,不被销毁 在我们的前面案例有一个问题,都知道vue的路由当我们切换一个路由后,另一个路由就会被销毁 ...
- Java高并发-Java内存模型和线程安全
一.原子性 原子性是指一个操作是不可中断的.即使在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰. i++是原子操作吗? 不是,包含3个操作:读i,i=i+1,写i 32位的机子上读取 ...
- Python实现将excel文件转化为html文件
需要转化的excel文件(nsrxx.xlsx): 源代码: import pandas as pdimport codecspd.set_option('display.width', 1000)p ...
- Java类包
学习内容:Java类包 一.Java类包 1.一个完整的类名需要包名和类名的组合,每一个类都隶属于一个包. 例:完整类名--java.sql.Date 2.同一个包中类相互访问时可以不指明包名. 3. ...
- 520表白小程序设计Python代码详解(PyQt5界面,B站动漫风)
摘要:介绍一个动漫风的表白小程序,界面使用Python以及PyQt实现,界面样式经过多次美化调整,使得整体清新美观.本文详细介绍代码设计和实现过程,不仅是居家表白必备,而且适合新入门的朋友学习界面设计 ...
- 『忘了再学』Shell基础 — 19、使用declare命令声明变量类型
目录 1.declare命令介绍 2.声明数组变量类型 3.声明变量为环境变量 4.声明只读属性 5.补充: 1.declare命令介绍 Shell中所有变量的默认类型是字符串类型,如果你需要进行特殊 ...