Hashmap完整解析

在1.8之后,Hashmap是由数组,链表,红黑树组成,
数组存储的元素是一个Entry类,这个类有三个数据域,key、value(键值对),next(指向下一个Entry)

hashmap首次初始化分为无参初始化和有参初始化两种情况;
很多情况下都是用无参构造方法创建,无参情况的默认容量大小为16,
另外一种就是给定一个默认容量,Hashmap要求初始大小必须是2的n次方,
若给定的值不是2的n次方,会执行tableSizeFor方法,经过转换后,将最终的结果赋值给 threshold变量,也就是初始容量,也就是本篇中所说的桶个数。

1.初始化容量大小
转换过程
ableSizeFor这个方法就有意思了,先把初始参数减 1,然后连着做或等于和无符号右移操作,最后算出一个接近的 2 的幂次方,下图演示了初始参数为 18 时的一系列操作,最后得出的初始大小为 32。
这样设计是为了保证寻找索引的散列计算更加均匀。
在这里插入图片描述

总是能得出不小于给定初始大小,并且最接近的2的n次方的最终值。

2.确定插入位置
当我们调用 put方法时,第一步是对 key 进行 hash 计算,hash 算法是这样的,拿到 key 的 hashCode,将 hashCode 做一次16位右位移,然后将右移的结果和 hashCode 做异或运算,这段代码叫做「扰动函数」,之所以不直接拿 hashCode 是为了增加随机性,减少哈希碰撞次数。

/**

  • 用来计算 key 的 hash 值
    **/
    static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    拿到这个 hash 值之后,会进行这样的运算 i = (n - 1) & hash,其中 i就是最终计算出来的索引位置。
    该桶位置无值,则进行插入,有值则发生hash碰撞

3.哈希碰撞(概率极低)
16个二进制位的哈希值,产生碰撞的可能性是 65536 分之一。也就是说,如果有65537个用户,就一定会产生碰撞。哈希值的长度扩大到32个二进制位,碰撞的可能性就会下降到 4,294,967,296 分之一。

不相等的对象使用 hash方法计算之后也有可能产生相同的值,这就叫做哈希碰撞。虽然通过算法已经很大程度上避免碰撞的发生,但是却无法避免。
哈希碰撞解决方法:
1,拉链法,形成链表,当链表长度到达8后,再有新元素加进来,那就要开始由链表到红黑树的转换了。方法 treeifyBin是完成这个过程的。

题外思考:
使用红黑树是出于性能方面的考虑,红黑树的查找速度要优于链表。那为什么不是一开始就直接生成红黑树,而是链表长度大于 8 之后才升级成树呢?
首先来说,哈希碰撞的概率还是很小的,大部分情况下都是一个桶装一个 Node,即便发生碰撞,都碰撞到一个桶的概率那就更是少之又少了,所以链表长度很少有机会能到 8 ,如果链表长度到 8 了,那说明当前 HashMap中的元素数量已经非常大了,那这时候用红黑树来提高性能是可取的。而反过来,如果 HashMap总的元素很少,即便用红黑树对性能的提升也不大,况且红黑树对空间的使用要比链表大很多。

4.扩容机制
阈值 = 容量 x 负载因子,假设当前 HashMap的容量是 16,负载因子是默认值 0.75,那么当 size 到达 16 x 0.75= 12 的时候,就会触发扩容。每次扩容为之前的两倍。扩容的过程中会对单节点类型元素进行重新计算索引位置,如果是红黑树节点则使用 split方法重新考量,是否将红黑树变为链表。

5.线程安全问题
HashMap没有做并发控制,如果想在多线程高并发环境下使用,请用 ConcurrentHashMap

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值