本篇博客主要讲解了HashMap的变量和构造器
这是本人第一篇关于源码解析的技术博客,由于个人技术的局限性可能也会出现一些错误。所以请大家看到能够帮忙指出,感激不尽。
HashMap变量详解
HashMap主要有以下几个变量:
1 | // 默认容器大小,1无符号右移4位,即16 |
以上有些需要补充说明一下:
默认负载因子”DEFAULT_LOAD_FACTOR”为什么是0.75。
这个可以参考一个小姐姐的简书,HashMap的loadFactor为什么是0.75?笔者还是从评论区那里看到知识点才开始阅读Integer源码的。为什么”MAXIMUM_CAPACITY = 1 << 30”。
我们知道,int在java占4字节32位,为什么不是”1 << 31”,那是因为在Java中,int的首位代表的是正负。如果右移31位那首位变成1,就是负数的最大值了。而且为了扩容时计算方便,HashMap采用的是与运算计算余数,所以规定扩容为2的幂次方,这个后续会讲。所以我们可以知道”1 << 30”已经是int类型中2的幂次方中最大的正整数。“MIN_TREEIFY_CAPACITY = 64”是什么意思。
MIN_TREEIFY_CAPACITY代表树化时容器的最小值,也就是不管HashMap里的元素个数有没有达到threshold的值,如果链表长度超过8个,在进行树化的时候就会判断这个值,低于64触发resize()方法。举个例子,如果很不幸你前8个都在entry数组的第一个bucket中(HashMap里面的数组的下标位置称为桶,存放Node节点),这个时候扩容阈值如果是默认的则为:(16*0.75=12)>8,远远没有达到扩容的要求,但是HashMap为了减少树化和解树化的开销,会进行resize操作。这个时候扩容成32了。链表转红黑树为什么是8,红黑树转链表为什么是6。
前者可以在HashMap源码开头的”Implementation notes”中找到,主要是如果Key使用的是一个好的hashCode算法,容器中超过8的概率只0.00000006,低于一千万分之一。但是你不能阻止用户使用差的hash算法,所以出于性能考虑转化值为8。至于为什么红黑树转换链表是6,这个目前笔者还不知道,不过网上有个很流行的说法是:红黑树的平均查找长度是log(n),如果长度为8,平均查找长度为log(8)=3,链表的平均查找长度为n/2,当长度为8时,平均查找长度为8/2=4,这才有转换成树的必要;链表长度如果是小于等于6,6/2=3,而log(6)=2.6,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短。
HashMap的构造器
HashMap的构造器涉及的方法如下:
1 | public HashMap(int initialCapacity, float loadFactor) { |
上述代码基本都有注释,我们也看到了,HashMap并没有在构造器中给table进行初始化,其实是因为HashMap把初始化延时到了put操作之中。要说明的重头戏是计算容量的算法,这个的确相当的厉害,本人其实也讲不太清,大家可以看一下Java8 HashMap之tableSizeFor这篇博客,我觉得讲解的很清晰了。
但是这个构造器有个BUG,那就是”this.threshold = tableSizeFor(initialCapacity)”实际上应该为”this.threshold = tableSizeFor(initialCapacity) * loadFactor”,这个在ORACLE官方社区上Doug Lea大佬亲自承认的bug,并且在JDK1.9修复了。当然,这并不影响大佬在我心中的高大形象。还有一点就是putMapEntries()方法中”threshold = tableSizeFor(t)”让我存疑,不应该为”threshold = tableSizeFor(t) * loadFactor”,这个没有在社区上看到大佬说是bug,但我感觉是和上一个犯了同一个错吧。
经过我多次的debug和源码查看,此处并不是一个bug。当初我在社区看到的bug是在构造器中有参传入(12)和有参传入(12,0.75)他们的初始容量不一致。具体数值我不太清楚了,但是doug lea大佬说这的确是一个bug,我下意识的以为是我认为的bug了,不过在深入了解之后发现有参构造的threshold是为了在put操作给table初始化容量用的,下一章会讲到。我会努力再找到这个问题,看看具体bug到底是什么。
还有一个有意思的地方是”Float.isNaN(loadFactor)”这个方法。给大家看看方法和示例:
1 | public static boolean isNaN(float v) { |
大家可以看看这个方法,想想输出会是什么呢?是报错还是输出true或者false呢?因为这个不在本文范畴内,所以给大家一个参考链接吧。大家先思考一下再带着疑问去有趣的NaN类型寻找答案吧。
本来打算今晚手撕红黑树,结果发现撕的脑壳疼,没有画图我自己都理不清。然而我还是个手残党不会画图,红黑树暂时不讲吧。今天的第一章暂时讲到这,明天我理一理应该按什么顺序讲好一点吧。
相关系列
基于JDK1.8的HashMap源码解析二(put和resize操作)
基于JDK1.8的HashMap源码解析三(get和remove以及迭代器)