当前位置: 首页 > news >正文

Java集合-HashMap扰动函数

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

上一篇文章HashMap内部结构提到了 HashMap 有一个扰动函数,来判断元素落在数组的位置。下面通过具体的例子说明。

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        
        ...
    }
    return null;
}

说明

get 方法如何确定key在数组中的位置,先通过 hash(key) 再通过 tab[(n-1) & hash] 来确定位置。

hash(key)

(h = key.hashCode) ^ (h >>> 16) 这个是什么意思?

h >>> 16 表示将hashCode的二进制码右移16位

^ 表示按位异或,也就是2个二进制码异或,2个数不同则结果为1,否则为0。

举个例子

混合高位和地位来加大随机性

 

table[(n-1) & hash]

那么 (n-1) & hash 又是什么意思?

n-1 表示map数组的长度减1

& 按位与,2个进制码相与,2个数相同则结果1,否则为0。

接着上面的例子,假设现在n等于map的默认长度 16

其实就是保留最后4位,将其他位都清零,再转换成10进制 0100就是4,也就是在 tab[4] 这个地方读取数据。如果进行了一次扩容那么数组的长度会扩展到32,这样就是根据二进制最后的5位来判断数组的位置(32 的二进制为 100000,31为 11111)。这也是为什么map数组的长度必需是2的n次方(a power of two),2的n次方-1 转换成二进制末尾都是1,长度不同。利用这种方式来使得插入的数据尽量不会落在同一个地方,均匀分布在数组的各个位置。

http://vanillajava.blogspot.com/2015/09/an-introduction-to-optimising-hashing.html

上面这篇文章详细的说明了 hash 策略,hash冲撞发生的概率。

 

实际场景模拟

通过自己写的简单代码模拟一下

String str1 = "abcd";
String str2 = "a";
String str3 = "cc";
String str4 = "d";

String[] table = new String[4];

System.out.println("str1 = " + str1);
System.out.println("str1.hashCode() = " + str1.hashCode());
System.out.println("str1.hashCode() >>> 16 = " + (str1.hashCode() >>> 16));
System.out.println("str1.hashCode() ^ (str1.hashCode() >>> 16) = " + ((str1.hashCode()) ^ (str1.hashCode() >>> 16)));
System.out.println("table.length - 1 = " + (table.length - 1));
System.out.println("(table.length - 1) & hash1 = " + ((table.length - 1) & ((str1.hashCode()) ^ (str1.hashCode() >>> 16))));
System.out.println();
System.out.println("str2 = " + str2);
System.out.println("str2.hashCode() = " + str2.hashCode());
System.out.println("str2.hashCode() >>> 16 = " + (str2.hashCode() >>> 16));
System.out.println("str2.hashCode() ^ (str2.hashCode() >>> 16) = " + ((str2.hashCode()) ^ (str2.hashCode() >>> 16)));
System.out.println("table.length - 1 = " + (table.length - 1));
System.out.println("(table.length - 1) & hash2 = " + ((table.length - 1) & ((str2.hashCode()) ^ (str2.hashCode() >>> 16))));
System.out.println();
System.out.println("str3 = " + str3);
System.out.println("str3.hashCode() = " + str3.hashCode());
System.out.println("str3.hashCode() >>> 16 = " + (str3.hashCode() >>> 16));
System.out.println("str3.hashCode() ^ (str3.hashCode() >>> 16) = " + ((str3.hashCode()) ^ (str3.hashCode() >>> 16)));
System.out.println("table.length - 1 = " + (table.length - 1));
System.out.println("(table.length - 1) & hash3 = " + ((table.length - 1) & ((str3.hashCode()) ^ (str3.hashCode() >>> 16))));
System.out.println();
System.out.println("str4 = " + str4);
System.out.println("str4.hashCode() = " + str4.hashCode());
System.out.println("str4.hashCode() >>> 16 = " + (str4.hashCode() >>> 16));
System.out.println("str4.hashCode() ^ (str4.hashCode() >>> 16) = " + ((str4.hashCode()) ^ (str4.hashCode() >>> 16)));
System.out.println("table.length - 1 = " + (table.length - 1));
System.out.println("(table.length - 1) & hash4 = " + ((table.length - 1) & ((str4.hashCode()) ^ (str4.hashCode() >>> 16))));


int hash1 = hash(str1);
int index1 = (table.length - 1) & hash1;
table[index1] = str1;

int hash2 = hash(str2);
int index2 = (table.length - 1) & hash2;
table[index2] = str2;

int hash3 = hash(str3);
int index3 = (table.length - 1) & hash3;
table[index3] = str3;

int hash4 = hash(str4);
int index4 = (table.length - 1) & hash4;
table[index4] = str4;

System.out.println(JSON.toJSONString(table));

初始化了一个长度为4的数组,利用扰动函数分别将 str1,str2,str3,str4插入到数组。

输出的结果是

str1 = abcd
str1.hashCode() = 2987074
str1.hashCode() >>> 16 = 45
str1.hashCode() ^ (str1.hashCode() >>> 16) = 2987119
table.length - 1 = 3
(table.length - 1) & hash1 = 3

str2 = a
str2.hashCode() = 97
str2.hashCode() >>> 16 = 0
str2.hashCode() ^ (str2.hashCode() >>> 16) = 97
table.length - 1 = 3
(table.length - 1) & hash2 = 1

str3 = cc
str3.hashCode() = 3168
str3.hashCode() >>> 16 = 0
str3.hashCode() ^ (str3.hashCode() >>> 16) = 3168
table.length - 1 = 3
(table.length - 1) & hash3 = 0

str4 = d
str4.hashCode() = 100
str4.hashCode() >>> 16 = 0
str4.hashCode() ^ (str4.hashCode() >>> 16) = 100
table.length - 1 = 3
(table.length - 1) & hash4 = 0

["d","a",null,"abcd"]

因为被输出到控制台,所以二进制被转换成10进制了,在计算机内部都是二进制计算的。但是不影响看结果,结果是cc字符串和d字符串计算出来数组的位置都是0,我这里是直接覆盖了,如果是 HashMap 的话,这里就要转换成链表了。

转载于:https://my.oschina.net/u/232911/blog/2254278

相关文章:

  • Django-jet自定义菜单
  • 解决org.apache.hadoop.hbase.MasterNotRunningException
  • Vue CLI 3开发中屏蔽烦人的EsLint错误
  • 搞定所有的跨域请求问题: jsonp CORS
  • 开源运维管理平台(ows) damo版本源码发布
  • 精彩回顾 | 阿里云APM城市技术行·深圳站
  • ballerina 学习 三十一 扩展开发(二)
  • ES7 之 Async/await 的使用(改进 Promise 链式操作)
  • LAMP+Postfix+Dovecot+Postfixadmin搭建邮件管理系统(三)
  • hadoop入门学习教程--DKHadoop完整安装步骤
  • webpack 4.x一起学习(二)
  • String类的常用方法详解
  • web开发原则
  • 视频文件应该怎样进行无损压缩
  • 删除小脚本 srm
  • 时间复杂度分析经典问题——最大子序列和
  • 30秒的PHP代码片段(1)数组 - Array
  • Android开源项目规范总结
  • CentOS从零开始部署Nodejs项目
  • CSS选择器——伪元素选择器之处理父元素高度及外边距溢出
  • express如何解决request entity too large问题
  • hadoop集群管理系统搭建规划说明
  • JS变量作用域
  • js数组之filter
  • JWT究竟是什么呢?
  • Linux编程学习笔记 | Linux多线程学习[2] - 线程的同步
  • Median of Two Sorted Arrays
  • Travix是如何部署应用程序到Kubernetes上的
  • vue-router 实现分析
  • 微信小程序上拉加载:onReachBottom详解+设置触发距离
  • 一个6年java程序员的工作感悟,写给还在迷茫的你
  • 做一名精致的JavaScripter 01:JavaScript简介
  • ionic入门之数据绑定显示-1
  • ​flutter 代码混淆
  • ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException
  • #{}和${}的区别?
  • #Lua:Lua调用C++生成的DLL库
  • #pragma once
  • #我与Java虚拟机的故事#连载05:Java虚拟机的修炼之道
  • (04)odoo视图操作
  • (13)Latex:基于ΤΕΧ的自动排版系统——写论文必备
  • (Pytorch框架)神经网络输出维度调试,做出我们自己的网络来!!(详细教程~)
  • (Redis使用系列) Springboot 整合Redisson 实现分布式锁 七
  • (六)vue-router+UI组件库
  • (七)c52学习之旅-中断
  • (未解决)jmeter报错之“请在微信客户端打开链接”
  • (转)jQuery 基础
  • (转)淘淘商城系列——使用Spring来管理Redis单机版和集群版
  • .NET Core实战项目之CMS 第十二章 开发篇-Dapper封装CURD及仓储代码生成器实现
  • .NET Entity FrameWork 总结 ,在项目中用处个人感觉不大。适合初级用用,不涉及到与数据库通信。
  • .NET Windows:删除文件夹后立即判断,有可能依然存在
  • .NET 常见的偏门问题
  • .NET 中选择合适的文件打开模式(CreateNew, Create, Open, OpenOrCreate, Truncate, Append)
  • .NET/C# 在 64 位进程中读取 32 位进程重定向后的注册表
  • .Net转Java自学之路—SpringMVC框架篇六(异常处理)