9 hashMap

hashMap

非线程安全,
hash冲突采用拉链法解决
多线程操作导致死循环问题主要原因在于并发下的 Rehash 会造成元素之间会形成一个循环链表https://coolshell.cn/articles... 这个写的不是很好

四个构造方法

1.将负载因子变量设为默认值0.75(默认情况下,HashMap 初始容量是16,默认的负载因子是0.75是因为权衡了时间和空间成本)
2.传入初始容量(hashMap里没有字段保存初始容量,构造方法将阈值设为比初始容量大的2的幂,当初始化桶数组时,设初始容量=阈值)+将负载因子设为默认值
3.传入初始容量和负载因子
4.将另一个 Map 中的映射拷贝一份到自己的存储结构中来

查找

先根据(n - 1)&hash定位桶,然后对链表或红黑树进行查找
HashMap 中桶数组的大小length总是2的幂(求位置的时候可以用位运算速度快),此时,(n - 1) & hash 等价于对 length 取余。取余的计算效率没有位运算高。只有n是2的幂次,才能用1111做位运算。
image.png

重新计算的 hash

图中的 hash 由键的 hashCode 产生。计算余数时,由于 n 比较小,hash 只有低4位参与了计算,导致高位数据没发挥作用。为了处理这个缺陷,我们以hash 高4位数据与低4位数据进行异或运算,即 hash ^ (hash >>> 4)。以此加大低位信息的随机性,变相的让高位数据参与到计算中。

遍历

遍历时顺序是稳定的,但是和插入顺序不一样
9 hashMap_第1张图片

插入

当桶数组 table 为空时,通过扩容的方式初始化 table
1查找要插入的键值对是否已经存在,存在的话根据条件判断是否用新值替换旧值
2如果不存在,则将键值对链入链表中,并根据链表长度决定是否将链表转为红黑树
3判断键值对数量是否大于阈值,大于的话则进行扩容操作

扩容

每次扩成原来的两倍
1计算新桶数组的容量 newCap 和新阈值 newThr
2根据计算出的 newCap 创建新的桶数组,桶数组 table 也是在这里进行初始化的
3将键值对节点重新映射到新的桶数组里。如果节点是 TreeNode 类型,则需要拆分红黑树。如果是普通节点,则节点按原顺序进行分组。

键值对节点重新映射的过程

对于树形节点,先拆分红黑树再映射。对于链表类型节点,则需先对链表进行分组,然后再映射。
假如是16个桶的map,每个hash值根据后四位分在16个桶里。当桶的数量扩充到32后,每个hash根据后5位分在32个桶里,所以原来一个桶的元素,只会进入固定的两个新桶而且相对位置不变。
1.7为了防碰撞,hash时加入随机种子以增加hash的随机性,扩容过程中会根据容量判断是否需要重新生成随机种子。1.8因为加了红黑树不怕碰撞了,所以不加随机种子,扩容时不需要重新hash,效率更高。

树化


链表长度大于等于 TREEIFY_THRESHOLD
桶数组容量大于等于 MIN_TREEIFY_CAPACITY
时,才能发生树化

树节点对比

HashMap 在设计之初,并没有考虑到以后会引入红黑树进行优化。所以并没有像 TreeMap 那样,要求键类实现 comparable 接口或提供相应的比较器。但由于树化过程需要比较两个键对象的大小,为了解决这个问题,HashMap 是做了三步处理,确保可以比较出两个键的大小,如下:
1比较键与键之间 hash 的大小,如果 hash 相同,继续往下比较
2检测键类是否实现了 Comparable 接口,如果实现调用 compareTo 方法进行比较
3如果仍未比较出大小,就需要进行仲裁了,仲裁方法为 tieBreakOrder(大家自己看源码吧)
tie break 是网球术语,可以理解为加时赛的意思,起这个名字还是挺有意思的。
?为什么比较键hash的大小可以比较键的大小?

红黑树拆分

在将普通链表转成红黑树时,HashMap 通过两个额外的引用 next 和 prev 保留了原链表的节点顺序。这样再对红黑树进行重新映射时,完全可以按照映射链表的方式进行。新链表如果太长,再进行一次树化。

桶不能序列化

桶数组 table 被申明为 transient。transient 表示易变的意思,在 Java 中,被该关键字修饰的变量不会被默认的序列化机制序列化。
HashMap 并没有使用默认的序列化机制,而是通过实现readObject/writeObject两个方法自定义了序列化的内容。
但序列化 talbe 存在着两个问题:
table 多数情况下是无法被存满的,序列化未使用的部分,浪费空间
同一个键值对在不同 JVM 下,所处的桶位置可能是不同的,在不同的 JVM 下反序列化 table 可能会发生错误。
如果键没有覆写 hashCode 方法,计算 hash 时最终调用 Object 中的 hashCode 方法。Object 中的 hashCode 方法是 native 型的,不同的 JVM 下,可能会有不同的实现,产生的 hash 可能也是不一样的。也就是说同一个键在不同平台下可能会产生不同的 hash。

你可能感兴趣的:(javahashmap)