基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用null值和 null键。(除了非同步和允许使用null 之外,HashMap 类与Hashtable大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 此实现假定哈希函数将元素适当地分布在各桶之间,可为基本操作(get 和 put)提供稳定的性能。迭代 collection 视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)成比例。所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。
### 1. HashMap的初始容量和加载因子的设置。
理想状态下,在哈希表单位空间内的存储节点符合泊松分布。参数lamada是0.5(在加载因子0.75的阀值下),公式如下:
exp(-0.5) * pow(0.5, k) /factorial(k).
泊松分布是指单位时间或单位空间内发生事情的概率,它的期望和方差都是lamada。这里具体指0.75的加载因子情况下,同一个桶发生哈希冲突的可能性
是0.5.默认容量是16,加载因子是0.75,意味着如果存的元素个数超过16*0.75=12
(threshod=capacity * load factor)将要rehash,这是非常消耗资源的,因为它将原来的结点遍历重新分布,所以初始化设置个数比较关键。
其中加载因子越大,虽省空间,但是哈希冲突大,get/put性能不好;加载因子过小又浪费空间。所以0.75是API推荐时间空间折中的数据。
同时指出,初始容量一定是2的次方,即使你写的不是最后也被写成了大于该数的最小的2的次方。容量是结点数组的长度,一般来说,为了hash更加分散,该长度会使用素数,但是这里没有。
它的hash定位,是使用key的hashcode高低位参与运算,再对该长度求余,最后定位到桶的位置。
关于泊松分布,传送阮一峰的两篇博客:
泊松分布和指数分布:10分钟教程
泊松分布与美国枪击案
其中当有哈希冲突时,原先做法都是添加到该桶的链表中,但是Java 1.8做了个优化,当元素不小于MIN_TREEIFY_CAPACITY=64时,
不小于链表的阀值(默认8)会把链表转为树(TreeMap),这样查询更快了。当该桶结点减少时,达到阀值(默认6)还会转化成链表。
2.HashMap的安全性
1)Hash
Collision DoS。有人曾用HashMap的冲突的弱点发起了Hash Collision DoS 攻击制造安全事故。通过提交表单(表单就是HashMap实现),
设计好极端的哈希冲突,在极端情况下,可以让全部结点hash到同一个桶位置的链表上,插入时间复杂度O(n),查询时间复杂度O(n).具体可以看陈皓博客:Hash Collision DoS 问题
2)HashMap本身不支持并发。多个线程下容易在resize的时候发生死循环,如果要支持并发,可以考虑ConcurrentHashMap,或者 Collections.synchronizedMap,或者通过底层提供的CAS实现,实现无锁HashMap的原理与实现。