一、HashMap的负载因子的作用

当 HashMap 中的元素个数(包含链表、红黑树上的元素)达到数组长度的0.75倍的时候,开始扩容。
 

二、HashMap的负载因子为什么是0.75

主要是为了提高空间利用率和减少查询成本(也可以说是尽可能减少hash冲突)。

三、为什么槽位数必须使用2^n

如果想让 Hash 结果分布更加均匀,首先想到的就是使用取余(%)操作。重点来了:“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash % length == hash & (length - 1) 的前提是 length 是 2 的 n 次方)。” 并且采用二进制位操作 &,相对于 % 能够提高运算效率,这就解释了 HashMap 的长度为什么是 2 的幂次方。

四、解决Hash冲突的方法

1、开放地址法

公式:fi(key) = (f(key)+di) MOD m (di=0,1,2,3,......,m-1)
key:待放入数组(hash表)的元素;m:数组长度
 
当冲突发生时,使用某种探测技术在散列表中形成一个探测序列。沿此序列逐个单元地查找,直到找到给定的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。查找时探测到开放的地址则表明表中无待查的关键字,即查找失败。

(1)线性探测法

思想是:通过公式计算出元素在数组中的下标,如果下标上没有元素,直接放进去;如果下标中有元素,则公式中的 di 依次 +1 重新计算,直到查找到没有元素的下标。不然数组就满了,需要扩容。

(2)二次探测法

思想是:通过改变 di 的计算方式来查询没有元素的下标,具体计算方式就是 di=-12,12,-22,22,…,-(q * 10 + 2),(q * 10 + 2),q <=m / 2。至于这个 di 的取值我也没研究,摘抄过来的,但是这个探测法的思想得知道。
考虑的情况是,如果通过公式计算出来下标之后的所有下标都有元素占据了,而这个下标的前面的有空闲的,通过第一种方法可以算出来,但是计算的次数比较多,通过这个方法可以减少计算次数。

(3)伪随机数探测再散列

思想是:di 的值是通过随机函数得到的。如果随机函数的种子相同,那么得出来的 di 也相同,查询就ok了。
 
总之,开放定址法只要在散列表未填满时,总是能找到不发生冲突的地址,是我们常用的解决冲突的办法。

2、拉链法

就是当产生 Hash 冲突时,在冲突的节点上形成链表,HashMap 就是使用的拉链法解决的 Hash 冲突。

五、为什么链表长度达到 8 的时候就要转为红黑树了?

当使用 0.75 作为负载因子时,链表中的长度达到 8 几乎是不可能的,均衡策略吧。
引用 HashMap 源码中的注释:
* 0:    0.60653066
* 1: 0.30326533
* 2: 0.07581633
* 3: 0.01263606
* 4: 0.00157952
* 5: 0.00015795
* 6: 0.00001316
* 7: 0.00000094
* 8: 0.00000006
* more: less than 1 in ten million 

六、HashMap扩容时元素的位置发生了什么变化?

分为三种情况:
  • 对于数组上的元素:直接使用已经计算出来的hash值重新计算新下标放入新数组。
  • 对于链表:将一条链表拆分为两条,hash值大于数组长度的新链表放在新数组,小于的就放在原数组。
  • 对于红黑树:将数拆为两条链表,hash值大于数组长度的新链表放在新数组,小于的就放在原数组,最后,重新判断两条链表是否需要转为红黑树。
关键代码:
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
例如:oldCap 是 16,那么扩容之后的新数组长度就是 32,链表上的元素分别是 7,23,39。(整数的hash值就是本身)
  7 :0000 0111
& 16:0001 0000
---------------
= :0000 0000 # 0,仍旧在原位 17:0001 0001
& 16:0001 0000
---------------
= :0001 0000 # 非0,需要放在 [17, 32) 之间 23:0001 0111
& 16:0001 0000
---------------
= :0001 0000 # 非0,需要放在 [17, 32) 之间 39:0010 0111
& 16:0001 0000
---------------
= :0000 0000 # 0,仍旧在原位,因为它的的值大于数组的长度

关于HashMap的一些思考的更多相关文章

  1. Java中HashMap扩容机制思考

    1. HashMap在什么条件下扩容 判断HashMap的数组Size大小如果超过loadFactor*capacity,就要扩容. 相关的类属性: capacity:当前数组容量,始终保持 2^n, ...

  2. 面试必问---HashMap原理分析

    一.HashMap的原理 众所周知,HashMap是用来存储Key-Value键值对的一种集合,这个键值对也叫做Entry,而每个Entry都是存储在数组当中,因此这个数组就是HashMap的主干.H ...

  3. 转 HashMap 比较透彻的分析

    HashMap 的实现原理 原文: HashMap 的实现原理 众所周知,HashMap是用来存储Key-Value键值对的一种集合,这个键值对也叫做Entry,而每个Entry都是存储在数组当中,因 ...

  4. 十个问题带你了解和掌握java HashMap

    十个问题带你了解和掌握java HashMap 一.前言 本篇内容是源于 " 由阿里巴巴Java开发规约HashMap条目引发的故事",并在此基础上加了自己的对HashMap更多的 ...

  5. 关于JDK1.7+中HashMap对红黑树场景的思考

    背景 在1.7之前的版本,当数组元素较多(几百.几千,或者更多)的时候,在这种前提扩容,涉及全量元素的遍历和坐标的重新定位,这个耗时会比较长.这是之前存在的一个弊端吧.那么引入红黑树之后就解决了问题, ...

  6. 关于HashMap中hash()函数的思考

    关于HashMap中hash()函数的思考 JDK7中hash函数的实现   static int hash(int h) { h ^= (h >>> 20) ^ (h >&g ...

  7. 关于Java中的HashMap的深浅拷贝的测试与几点思考

    0.前言 工作忙起来后,许久不看算法,竟然DFA敏感词算法都要看好一阵才能理解...真是和三阶魔方还原手法一样,田园将芜,非常可惜啊. 在DFA算法中,第一步是需要理解它的数据结构,在此基础上,涉及到 ...

  8. 【集合框架】JDK1.8源码分析之HashMap(一)

    一.前言 在分析jdk1.8后的HashMap源码时,发现网上好多分析都是基于之前的jdk,而Java8的HashMap对之前做了较大的优化,其中最重要的一个优化就是桶中的元素不再唯一按照链表组合,也 ...

  9. Java之HashMap在多线程情况下导致死循环的问题

    PS:不得不说Java编程思想这本书是真心强大.. 学习内容: 1.HashMap<K,V>在多线程的情况下出现的死循环现象   当初学Java的时候只是知道HashMap<K,V& ...

随机推荐

  1. SwiftUI error All In One

    SwiftUI error All In One Instance member xxx cannot be used on type yyy Instance member 'game' canno ...

  2. Jupyter All In One

    Jupyter All In One Jupyter Architecture https://jupyter.readthedocs.io/en/latest/projects/architectu ...

  3. flutter & plugins

    flutter & plugins https://pub.dev/ https://juejin.im/post/5c206b4ff265da61327f52f4

  4. ES6 Arrow Function return Object

    ES6 Arrow Function return Object https://github.com/lydiahallie/javascript-questions/issues/220#issu ...

  5. Flutter: provider 使用小部件的小部件构建的依赖注入系统

    文档 dependencies: provider: import 'package:dart_printf/dart_printf.dart'; import 'package:flutter/ma ...

  6. go-admin在线开发平台学习-4[细节解析]

    紧接着上一期的内容,继续对go-admin的一些细节进行解析. 通用的增删改查方法 在go-admin/common/actions下封装了通用的增删改查方法.在对这些方法进行说明前,先移步到另一个文 ...

  7. IDEA 敏捷开发技巧——后缀完成

    前言 "工欲善其事,必先利其器." 所以说今天来看一看如何压榨 IDEA ,让你的 IDEA 使用的更顺手! 今日技巧: 后缀完成 自定义后缀完成模版 示例 上面动图使用了 .so ...

  8. Java SE7虚拟机指令操作码助记符

    本文转载自Java SE7 虚拟机指令操作码助记符 导语 在Class文件中,Java方法里的方法体,也就是代表着一个Java源码程序中程序的部分存储在方法表集合的Code属性中.存储在Code属性中 ...

  9. python进阶(3)序列化与反序列化

    序列化与反序列化 按照某种规则,把内存中的数据保存到文件中,文件是一个字节序列,所以必须要把内存数据转换成为字节序列,输出到文件,这就是序列化:反之,从文件的字节恢复到内存,就是反序列化: pytho ...

  10. Docker Elasticsearch 集群配置

    一:选用ES原因 公司项目有些mysql的表数据已经超过5百万了,各种业务的查询入库压力已经凸显出来,初步打算将一个月前的数据迁移到ES中,mysql的老数据就物理删除掉. 首先是ES使用起来比较方便 ...