首先我们要知道,在理想情况下的哈希表中,哈希函数生成的哈希值是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中的哈希函数分析的更多相关文章

  1. HashMap 中的哈希值计算问题

    date: 2020-08-21 16:48:00 updated: 2020-08-21 16:52:00 HashMap 中的哈希值计算问题 1. hash 计算 JDK1.8 HashMap源码 ...

  2. Java中String的hash函数分析

    转载自:http://blog.csdn.net/hengyunabc/article/details/7198533 JDK6的源码: [java] view plaincopy /** * Ret ...

  3. openstack(liberty): devstack中的iniset/iniget函数分析

    这个ini开头的函数在devstack的启动配置中用的非常多,他主要负责.ini文件的配置,这个过程包括对相关ini文件的添加,注释,删除,获取信息,多行信息获取等. 这里主要说的iniset和ini ...

  4. C++中的内联函数分析

    1,本节课学习 C++ 中才引入的新的概念,内联函数: 2,常量与宏回顾: 1,C++ 中的 const 常量可以替代宏常数定义,如: 1,const int A = 3; <==> #d ...

  5. HashMap在并发下可能出现的问题分析

    我们都知道,HashMap在并发环境下使用可能出现问题,但是具体表现,以及为什么出现并发问题,可能并不是所有人都了解,这篇文章记录一下HashMap在多线程环境下可能出现的问题以及如何避免. 在分析H ...

  6. HashMap的小总结 + 源码分析

    一.HashMap的原理 所谓Map,就是关联数组,存的是键值对——key&value. 实现一个简单的Map,你也许会直接用两个LIst,一个存key,一个存value.然后做查询或者get ...

  7. EPANET中的哈希文件——hash.c

    /*-----------------------------------------------------------------------------**   hash.c****   Imp ...

  8. 【C# 集合】Hash哈希函数 |散列函数|摘要算法

    希函数定义 哈希函数(英語:Hash function)又称散列函数.散列函数.摘要算法.单向散列函数.散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来.该函数将数据打乱混合,重新 ...

  9. 2、JDK8中的HashMap实现原理及源码分析

    本篇提纲.png 本篇所述源码基于JDK1.8.0_121 在写上一篇线性表的文章的时候,笔者看的是Android源码中support24中的Java代码,当时发现这个ArrayList和Linked ...

随机推荐

  1. LintCode-165 · 合并两个排序链表-题解

    描述:将两个排序(升序)链表合并为一个新的升序排序链表样例 1:输入: list1 = null, list2 = 0->3->3->null输出: 0->3->3-&g ...

  2. 团队Arpha5

    队名:观光队 组长博客 作业博客 组员实践情况 王耀鑫 **过去两天完成了哪些任务 ** 文字/口头描述 完成服务器连接数据库部分代码 展示GitHub当日代码/文档签入记录 接下来的计划 服务器网络 ...

  3. 2020 最烂密码 TOP 200 大曝光!

    点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 整理 | 王晓曼 出品 | 程序人生 (ID:coder ...

  4. RabbitMQ从概念到使用、从Docker安装到RabbitMQ整合Springboot【1.5w字保姆级教学】

    @ 目录 一.前言 二.RabbitMQ作用 1. 异步处理 2. 应用解耦 3. 流量控制 三.RabbitMQ概念 1. RabbitMQ简介 2. 核心概念 四.JMS与AMQP比较 五.Rab ...

  5. vue - Vue路由(扩展)

    忙里偷闲,还在学校,趁机把后面的路由多出来的知识点学完 十.缓存路由组件 让不展示的路由组件保持挂载,不被销毁 在我们的前面案例有一个问题,都知道vue的路由当我们切换一个路由后,另一个路由就会被销毁 ...

  6. Java高并发-Java内存模型和线程安全

    一.原子性 原子性是指一个操作是不可中断的.即使在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰. i++是原子操作吗? 不是,包含3个操作:读i,i=i+1,写i 32位的机子上读取 ...

  7. Python实现将excel文件转化为html文件

    需要转化的excel文件(nsrxx.xlsx): 源代码: import pandas as pdimport codecspd.set_option('display.width', 1000)p ...

  8. Java类包

    学习内容:Java类包 一.Java类包 1.一个完整的类名需要包名和类名的组合,每一个类都隶属于一个包. 例:完整类名--java.sql.Date 2.同一个包中类相互访问时可以不指明包名. 3. ...

  9. 520表白小程序设计Python代码详解(PyQt5界面,B站动漫风)

    摘要:介绍一个动漫风的表白小程序,界面使用Python以及PyQt实现,界面样式经过多次美化调整,使得整体清新美观.本文详细介绍代码设计和实现过程,不仅是居家表白必备,而且适合新入门的朋友学习界面设计 ...

  10. 『忘了再学』Shell基础 — 19、使用declare命令声明变量类型

    目录 1.declare命令介绍 2.声明数组变量类型 3.声明变量为环境变量 4.声明只读属性 5.补充: 1.declare命令介绍 Shell中所有变量的默认类型是字符串类型,如果你需要进行特殊 ...