注:本文基于JDK1.8

HashMap知识点较多,一次性讲解篇幅较大,不便于记忆,我决定使用定点突破的方式进行讲解。

读完本篇文章你将了解到:

  • HashMap中的位运算

  • HasMap的构造函数

  • HashMap中容量为什么是2的次方数

HashMap中的位运算

位运算计算效率高,Jdk开发人员在HashMap源码中使用了大量的位运算。

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16static final int MAXIMUM_CAPACITY = 1 << 30;static final int tableSizeFor(int cap) {        int n = cap - 1;        n |= n >>> 1;        n |= n >>> 2;        n |= n >>> 4;        n |= n >>> 8;        n |= n >>> 16;        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;    }

在进行分析之前我们来对位运算进行简单复习。

  • 【与】    运算    & :特点 -->遇0得0 (相乘)

    例:3&5

    3:      0000 0011

    5:      0000 0101

    结果  0000 0001

  • 【或】    运算    | :特点 -->遇1得1(相加)

    例:3|5

    3:      0000 0011

    5:      0000 0101

    0000 0111

  • 【异或】运算    ^ :特点 -->相同为0 ,不同为1”(相减)

    例:3^5

    3:      0000 0011

    5:      0000 0101

    0000 0110

  • 【非】    运算    ~ :特点 -->各位取反

HashMap中的构造函数

在讲解HashMap构造函数之前,我们先快速浏览一遍HashMap比较重要的几个属性。

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认的数组初始化容量
static final int MAXIMUM_CAPACITY = 1 << 30; //默认的数组最大容量
static final float DEFAULT_LOAD_FACTOR = 0.75f; //默认负载因子
static final int TREEIFY_THRESHOLD = 8;  //链表树化阈值
static final int UNTREEIFY_THRESHOLD = 6; //树转链表阈值 <6
static final int MIN_TREEIFY_CAPACITY = 64; //最小树形化容量阈值 当哈希表中的容量 > 该值时,才允许树形化链表  否则进行数组的扩容
HashMap的有参构造函数

我们进行无参构造时,使用的是其默认的容量及负载因子

public HashMap(int initialCapacity, float loadFactor) {    if (initialCapacity < 0)        throw new IllegalArgumentException("Illegal initial capacity: " +                                           initialCapacity);    if (initialCapacity > MAXIMUM_CAPACITY)        initialCapacity = MAXIMUM_CAPACITY;    if (loadFactor <= 0 || Float.isNaN(loadFactor))        throw new IllegalArgumentException("Illegal load factor: " +                                           loadFactor);    //上面都是一些合法性检查      this.loadFactor = loadFactor;    //这里threshold 是通过 tableSizeFor()来获取一个>= initialCapacity的二进制数    //其作为扩容阈值的作用会在resize() 方法中重新赋值    this.threshold = tableSizeFor(initialCapacity);}

我们看到在HashMap的构造函数中并没有进行数组的初始化,它是懒加载的在第一次put数据的时候进行初始化。

以下为putVal()方法部分代码

if ((tab = table) == null || (n = tab.length) == 0)            n = (tab = resize()).length;  //进行初始化
tableSizeFor(initialCapacity)方法
static final int tableSizeFor(int cap) {    int n = cap - 1;    n |= n >>> 1;    n |= n >>> 2;    n |= n >>> 4;    n |= n >>> 8;    n |= n >>> 16;    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;}

这个方法会返回一个大于等于入参的一个2的次方数,比如 5->8  10->16 16->16。

而为什么需要数组长度是一个2的次方数,我们先留个疑问稍后解答。

我们看到他先将入参cap进行减1  再进行无符号右移(高位全补0)  或运算  他这是再干嘛??我们来运算试一下。

假设  8

第一次 |运算

n :  00... 0000 1***  (32位)

右移 1 位 n1 :   00... 0000 0111

r:   00... 0000 11**

第二次 |运算

n1 :  00... 0000 11**

右移 2 位 n2 :   00... 0000 0011

r:   00... 0000 1111

发现了没有,经过右移或运算不断把第一个1 后面的值都变为1

return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;

最后判断如果 n>=0  返回 n+1  那么 将会变成 0001 0000 -->16 。妙啊!

至于开始的n = cap - 1  是为了边界值  如输入cap = 16 如果不进行-1 最终结果将是32  。

为什么数组长度是2的次方数

我们先看put方法中的一个小代码片段,是当计算出的数组下表中还没有数据存储,直接将node存储到数组的下标位置。

if ((p = tab[i = (n - 1) & hash]) == null)            tab[i] = newNode(hash, key, value, null);

n为数组长度。

那么计算value存储在哪个数组下标是一个关键。我们通常可以使用 hash%n 来得出具体位置,,但HashMap中不是这么做的,她是通过过【  i = (n - 1) & hash 】,通过hash值和n-1 进行&运算,这种方式效率高,那么为什么将hash值与n-1就能确定他的位置呢?

假设 n = 16

n-1: 00... 0000 1111

h : 01... 1010 0101

res: 00... 0000 0101

n-1: 00... 0000 1111

h : 01... 1010 0111

res: 00... 0000 0111

n-1: 00... 0000 1111

h : 01... 1010 1111

res: 00... 0000 1111

我们看到在进行了&操作后,hash值得高位都被丢弃掉了,而&操作后的取值范围 是 0000  --> 1111也就是 0- >15  。妙啊!

这样就确定了一个hash值在数组中的位置,完事了?

NO NO  相信有人已经想到了,那这不就是只要hash的低位吗,高位有差异也被直接丢弃了,这不行啊,hash碰撞的几率更高了!

这一点JDK开发人员早就想到了,在进行数组下标计算之前,对原有hashcode进行了再次加工,将hash值 和  hash右移16位后的新值进行 ^ 操作。

return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

通过这种方式,使得原有hashcode的高16位也参与到计算中来,这样可以大大降低hash碰撞的几率。

说回到上面的问题,为什么数组的长度要保证是2的次方呢,这就是为了高效计算数组下标!

如果n不是2的次方数。

n-1: 00... 0001 0000

h : 01... 1010 0111

res: 00... 0000 0000     高位低位怎么变化都是0 0000 或 1 0000 这肯定是不行的。

这里是有规律的 ,只有拿2的次方数 -1 的得的值(0011 1111)进行位运算,取值范围才是正确的,才能得到与 hash%n 一样的效果。

关于HashMap的其他知识点,将在后面推出,敬请期待。

bigdecimal计算开n次方_随笔:HashMap中容量为什么是2的次方数?相关推荐

  1. bigdecimal计算开n次方_“家的N次方”张家口华耐家居员工年会盛典圆满落幕

    光阴荏苒,岁月如梭. 2020年1月9日,张家口华耐家居年度盛会在张家口威尼斯大酒店举行,华耐家居四店.销售体系.房开体系的全体员工欢聚一堂,共同度过"家的N次方"主题年度盛会. ...

  2. hashmap为什么容量是2的n次方

    我们知道在hashmap中要找到某个元素,需要根据key的hash值来求得对应数组中的位置.如何计算这个位置就是hash算法.前面说过hashmap的数据结构是数组和链表的结合,所以我们当然希望这个h ...

  3. bigdecimal计算开n次方_学会使用基从考试电脑计算器,提高过关率

    海绵金融从业考试 微信号:haimianjinrong66 关注 基金从业考试计算器使用方法 基金从业考试是不可以自己带计算器,只准携带:演算笔.准考证.身份证件等.要使用计算器,电脑上的计算器一般都 ...

  4. ios开发中计算代码运算时间_理解Unity中的优化(二):内存

    内存: 内存消耗是一个关键的性能指标,尤其是在内存资源有限的平台上,比如低端移动设备. 内存消耗分析: 在Unity中诊断内存问题,Unity介绍了一款开元的可视化内存分析工具--MemoryProf ...

  5. java如何实现e的次方_在java中如何输入e^x(x为变量)还有如何输入e(e为自然对数的底数)?...

    展开全部 1. java中的a^b a的b次方32313133353236313431303231363533e59b9ee7ad9431333363373766, 可以使用Math.pow(a,b) ...

  6. java 计算股票高低点_[转载]股市中常用的一些计算高低点的计算方法

    朋友们观看股评文章时,常常发现那些专家或准专家发出股价在上涨到某某价位会遇到阻力或股价在下跌途中在某一价位会遇到有效支撑的判断,而股价也真的会在这一价位附近掉头向下(上涨途中)或横盘盘整.或下跌途中的 ...

  7. longtext长度为0是什么意思_为什么 HashMap 中链表长度大于 8 才转化为红黑树?

    Java 中的 HashMap 采用链表法来解决哈希冲突(HashMap 原理),即具有相同桶下标的键值对使用一个链表储存.当链表变长时,查找和添加(需要确定 key 是否已经存在)都需要遍历这个链表 ...

  8. python中计算列表的平均值_如何计算python中元组列表的平均值?

    你想做什么还不太清楚.一些示例代码会有所帮助,并提供一些您尝试过的信息.即使你的方法是完全错误的,它也会让我们对你的目标有一个模糊的概念.在 同时,也许numpy的numpy.mean函数适合您的问题 ...

  9. hashmap中的key是有序的么_关于HashMap中KEY的有序排列的反思和总结(对应TreeMap)

    /对HashMap,key为其他字符的测试 显示多字符串:写入,读取,无序 单字符:写入无序,读取按照key顺序排列 单数字:写入无序,读取按照key顺序排列/ import java.util.Ha ...

  10. java判断2的n次方_判断一个正整数是否是2的N次方的简洁算法及其证明

    在写代码时遇到了"判断一个正整数是否是2的N次方"的问题,不想调用 java.lang 的 Math 类库进行浮点运算,觉得转换为浮点不是个好办法. 遂在网上搜索了一下,发现有人列 ...

最新文章

  1. 谈C++求a+b(大神勿喷)
  2. Asp.net中文件上传下载的简单实现
  3. JavaScript将字符串中的每一个单词的第一个字母变为大写其余均为小写
  4. ECharts 联动效果
  5. PyG图神经网络框架--构建信息传递网络(MPN)
  6. GridView的操作:导出Excel[方案一]
  7. 中国数据中心行业深度分析
  8. 用pc浏览器打开手机页面
  9. java预科_java复习预科知识-Markdown学习
  10. oracle 模拟 mysql,mysql通过表和function模拟oracle的sequence
  11. 正确姿势使用TraceView工具
  12. Camera_Hal3_User_Manual
  13. html5-移动端布局模板
  14. linux交叉编译libnet,交叉编译samba(mipsel-linux) samba-3.3.3.tar.gz
  15. 小谈国内桌面浏览器占有率
  16. Mysql索引结构全维度比较
  17. 使用Linux命令cURL实现文件定时上传到ftp服务器的程序
  18. setCapture和releaseCapture的小应用
  19. Python写自动化之图标锁定到任务栏或删除图标
  20. initpki.dll加载失败 找不到指定的模块的解决办法

热门文章

  1. Root Pane Container(三)
  2. win7的配置要求详解
  3. 我的2006总结:一个结束和一个开始
  4. QProcess解决无交互输入密码问题
  5. 什么是Prettier?
  6. python print(chr(65))_python 内置函数
  7. javax.servlet.http.HttpServletResponse.setContentLengthLong(J)V,maven项目报错!!无法访问webapp下的文件,完美解决方案
  8. HTML5求自动在闪,HTML5 重复而不停闪烁的团状物
  9. 【转】vue双向绑定原理分析
  10. ASP.NET 2.0运行原理及其过程简要分析