一、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. LeetCode 数组分割

    LeetCode 数组分割 LeetCode 数组怎么分割可以得到左右最大值的差值的最大 https://www.nowcoder.com/study/live/489/1/1 左右最值最大差 htt ...

  2. we have a problem with promise

    we have a problem with promise Q: What is the difference between these four promises? doSomething() ...

  3. vscode & javascript & react & "editor.foldingStrategy": "indentation"

    vscode & javascript & react & "editor.foldingStrategy": "indentation" ...

  4. Rollup & Webpack & Parcel

    Rollup & Webpack & Parcel package bundler https://x-team.com/blog/rollup-webpack-parcel-comp ...

  5. perl 打印简单的help文档

    更多 PrintHelp.pm #!/usr/bin/perl package PrintHelp; require Exporter; use v5.26; use strict; use utf8 ...

  6. android adb命令,向开发手机添加文件

    adb文档 把本地文件发送到调试手机 C:\Users\ajanuw>adb push C:\Users\ajanuw\Music\j.mp3 /storage/emulated/0/Downl ...

  7. 【java】ObjectOutputStream & ObjectInputStream 多次写入发生重复写入相同数据的问题

    今日份代码,解决 ObjectOutputStream 多次写入发生重复写入相同数据的问题 核心区别如下: package com.sxd.swapping.objoutputstream; impo ...

  8. 05_MySQL什么是sql语句

    什么是SQL语言 SQL语言的分类 SQL语言的注意事项 SQL语句的注释 数据的创建.查看及删除 实操: 创建数据表 实操: 数据表的其他操作 查看数据库的数据表 查看表结构: 查看建表语句:

  9. C# NOPI 项目实战(经典)(可下载项目源码)

    1 -.首先说明下项目目的: 之前我有写过一篇  "NPOI操作EXCEL" 这篇文章主要介绍了如何安装NPOI,以及NPOI具体如何使用,并且用具体实例介绍了excel导入到da ...

  10. Django Admin 后台Admin继承UserAdmin增加用户密码不显示明文和用户登录不了的解决方法

    Django后台Admin继承UserAdmin增加用户不显示明文方法 1.在 models.py 中用户表 # 导包规范-1.Python标准模块 from django.db import mod ...