【JAVA集合】HashMap

HashMap

HashMap的数据结构

  • JDK1.7 的数据结构是 数组 + 链表
  • JDK1.8 的数据结构是 数组 + 链表 + 红黑树
    在这里插入图片描述

桶数组是用来存储数据元素,链表是用来解决冲突,红黑树是为了提高查询 的效率。

元素通过计算哈希公式得到具体位置,存储到数组中,但数组中已有元素占用位置,则用链表来存放发生冲突的元素,当链表长度大于8时,则会将链表转化为红黑树,提高查询效率。

HashMap的Put操作

  1. 判断当前table也就是数组是否为空,为空则进行初始化,扩容也就是调用resize方法
  2. 根据计算出来的hash值 i = ((n - 1) & hash索引,判断当前table[ i ] 是不是为空,为空直接插入
  3. 接下来就是桶内操作了(一条列表、一颗红黑树或称为桶),定义一个节点e 作为工具人,暂存找到相同hash的节点。
  4. 获取当前数组table[i] 的节点 p,首先判断是否等于putKey的hash,相等将p节点赋予e节点
  5. 首节点hash 与 key 的hash 不相等,然后判断其是不是红黑树节点,是就进去遍历树节点,有就返回给e节点,没就先创建新节点返回传给 e 节点。
  6. 不是树节点,那就只能是链表结构了,遍历链表,寻找节点,也是有就返回给e节点,没就先创建新节点返回传给 e 节点,这里有一个判断,其链表长度是否到了可以转为树的判断,够长了就转红黑树。
  7. 随后,判断e节点工具人是否为空,不为空赋值操作,覆盖掉记录。
  8. 判断容量是否需要扩容。
    在这里插入图片描述

HashMap怎么查找元素的呢?

寻找元素源码:

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {  // 判断数组是否为空 并且得到数组中的下标而元素不为空
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first; // 判断数组中的元素是否是目标元素
        if ((e = first.next) != null) {  // 下一个节点不为空
            if (first instanceof TreeNode)  // 是数子节点,遍历红黑树
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do { // 否则遍历链表
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}
  1. 使用扰动函数,获取新的哈希值
  2. 计算数组下标,获取节点
  3. 当前节点和key匹配,直接返回
  4. 否则,当前节点是否为树节点,查找红黑树
  5. 否则,遍历链表查找
    在这里插入图片描述

以下是一些常见的哈希函数构造方法:

  1. 除余法:这是构造哈希函数最常见的方法,一般选取一个素数去取余。因为约数越多,其分布在hash表中的位置就会不均匀,冲突的几率就会提高。
  2. 数字分析法:这种构造方法适用于关键字的位数比地址的位数多且某几位数字区别不大或就是相同的,那么此时可以去掉这些相近的数位,用余下的数位来进行hash。
  3. 平方取中法:将关键字平方然后取其中的几位作为地址。
  4. 折叠法:与数字分析的条件略有不同,这种方法试用于数位较多且每位分布均匀。
  5. 随机数法:注意random的随机种子需要是固定的,以便查询的时候能够根据key重新找到存储位置。

HashMap的扰动函数

HashMap中的扰动函数(Perturbation Function)用于处理哈希冲突,确保数据在哈希表中的均匀分布。在Java的HashMap中,扰动函数的设计如下:
扰动函数的核心思想是将哈希值进行混淆,使得哈希值在不同位置上的分布更加均匀。HashMap的扰动函数采用了一种简单的设计,通过将哈希值与高常数(默认为16)进行异或(XOR)运算来实现混淆。
具体来说,HashMap的扰动函数的实现如下:

static final int hash(Object key) {  
    int h;  
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);  
}

这个函数接受一个对象作为参数,首先获取对象的哈希码(hashCode()),然后使用无符号右移运算符(>>>)将哈希码向右移动16位,然后再与原始哈希码进行异或运算。

这种设计的目的是将高位的数据与低位的数据进行混合,从而增加哈希值的随机性,减少哈希冲突。同时,这种设计也保证了在不同长度的哈希值上具有良好的性能。

为什么HashMap链表转红黑树的阈值为8呢?

这个阈值的设定是基于性能考虑的。

● 当链表长度超过8时,链表查找的效率会变得较低,因为需要遍历链表来查找目标元素。
● 红黑树是一种自平衡的二叉搜索树,可以在O(log n)的时间复杂度内进行查找、插入和删除操作,效率比链表要高。
当链表长度超过8时,转换为红黑树可以提高查找效率。如果阈值设定过小,链表转换为红黑树的频率会增加,而红黑树的维护成本相对较高,可能会导致性能下降。如果阈值设定过大,链表查找的效率会降低,从而影响HashMap的整体性能。
因此,HashMap链表转红黑树的阈值设定为8是一个经验值,旨在平衡链表和红黑树的使用效率

HashMap为什么扩容因子是0.75?

HashMap的扩容因子(Load Factor)为0.75,意味着当HashMap的存储空间达到数组长度的0.75倍时,会进行扩容操作。
扩容因子的设定是基于性能和数据均匀性考虑的。

● 空间利用率
扩容因子的大小决定了HashMap的空间利用率。如果扩容因子设置得过高,HashMap的空间利用率会提高,但可能会导致更多的哈希冲突,从而降低性能。如果扩容因子设置得过低,HashMap的空间利用率会降低,虽然可以减少哈希冲突,但会浪费存储空间。
● 数据均匀性
0.75的扩容因子可以使得HashMap在扩容时保持较好的数据均匀性。当HashMap进行扩容时,原有的数据需要重新计算哈希值并分配到新的存储位置。如果扩容因子过高,数据在重新分配时可能会出现聚集现象,导致数据均匀性下降。而0.75的扩容因子可以在保持较高空间利用率的同时,避免数据聚集现象的发生。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

莫子莫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值