hashMap源码解读之Java jdk1.7

核心参数

hashmap其内部有

  • threhold:实际的负载量,超过了这个值,就会扩容。
  • loadFactor:扩容因子。
  • 真实容量(capacity) * loadFactor = threhold
  • Entry<K,V>[] 这是个Entry<K,V>的数组,代表了整个hash表,其中每一个Entry<K,V>代表一个桶,他是一个链表。正因如此,即使发生了哈希冲突,也可以放在同一个桶中。
  • modCount
    我们知道HashMap不是线程安全的 , 也就是说你在操作的同时可能会有其它的线程也在操作该map, 用modCount来记录修改集合修改次数。
    我们在边迭代边删除集合元素时会碰到一个异常ConcurrentModificationException , 原因是不管你使用entrySet()方法也好 , keySet()方法也好 , 其实在for循环的时候还是会使用该集合内置的Iterator迭代器中的nextEntry()方法 , 如果你没有使用Iterator内置的remove()方法 , 那么迭代器内部的记录更改次数的值便不会被同步 , 当你下一次循环时调用nextEntry()方法便会抛出异常。这和我们学习ArrayList里面的原理是一样的。ArrayList学习笔记

构造

  1. 首先我们new一个hashMap,点进去
    在这里插入图片描述
    如下:
    在这里插入图片描述
    查看两个默认值看到一个为16,一个为0.75 ,这也就是一会要赋给的threholdloadFactor的默认值
    在这里插入图片描述
  2. 在下面这个构造函数我们可以看到,把传进来的两个默认值分别赋给了threhold和loadFactor
    在这里插入图片描述
  • new hashMap无参构造会把默认的DEFAULT_INITIAL_CAPACITY 16 赋给 threshold。会把DEFAULT_LOAD_FACTOR 0.75 赋给loadFactor。
  • 会在首次put时把threshold设置为 16 * 0.75.

put方法详解

  • 首先看到put方法主体
    在这里插入图片描述

  • 如果map是空的话 会初始化 通过inflateTable(threshold);
    在这里插入图片描述
    将原本容量转化成二进制 (向上取整)赋值给了capacity,作为实际容量,如 7转化成8 9转化成16具体原因下面会讲。
    然后将threshold 变成了 新的容量 capacity 的loadFactor(扩容因子)倍 。

  • 第一次put时会初始化数组,其容量变为不小于指定容量的2的幂数。然后根据负载因子确定阈值。

  • 如果键值为空,会进入如下方法:
    在这里插入图片描述
    这个方法所代表的含义就是,查看整个hash表的第一个桶(是个链表),看这个链表中有没有键值为空的,如果有,就将val赋值给它。如果没有,就进入addEntry方法。方法如下:
    在这里插入图片描述
    这个方法所代表的含义就是:如果负载即threhold没有满,或者size超过负载量了但指定的桶中为空,那么进入createEntry方法:在当前桶中头插入这个节点。
    如果size超过负载了,并且指定的桶有元素了就会进入if里面:先扩容2倍,在重新计算hash值,然后根据新的hash值获得桶的索引,再次插入。

  • 如果键值不是空的,就会根据键值计算出hash,然后根据hash计算出索引。
    计算索引方法如下:
    在这里插入图片描述
    这也是为什么必须要求hash表的长度是2的幂次方。再减一之后,除了最高位,其余位都是1,在和hash相与,得出结果。 (这个过程就是求余) 我们可以看到如果容量是2的次方 , 那么length - 1得到的二进制的除了补位外都是1,根据&运算符的规则 , 0&0=0; 0&1=0; 1&1=1;那么也就意味着不论h的值是什么 , 只要length - 1的二进制码是这样的规律的 , 那么就 可以保证hash的值只有和length - 1的同位参与了运算 , 例如二进制码A(10101011)&B(00001111)的结果就是
    C(00001011) , C的结果只会受到b二进制码后四位的影响,因为b的补位都是0 , 也就是说h & (length - 1)得到的索引不会大于length,也就不会越界。
    在这里插入图片描述

计算出索引之后就进入了for循环。for循环 遍历的是上面计算出来的桶索引代表的那个链表的所有节点。然后再判断这个链表里面有没有 和要插入的节点hash值相同并且equals或者==结果也相同的节点,如共有,就覆盖,并且返回旧值。否则就插入节点。

get方法详解

  • get方法相比来说就比较简单了
    我们点进get方法首先看到:
    在这里插入图片描述
    如果键值时空的话,会走如下方法
    在这里插入图片描述
    这个和刚才看的put方法一致,都在再桶索引为0的地方存取,不用多说。
    当键值不为空的时候进入下面方法
    在这里插入图片描述
    这个方法的含义是:首先根据键值计算出hash,然后根据hash计算出索引找到桶,遍历链表的每个节点,当遇到和目标hash相同并且key相同或者equals的节点,就返回。

resize方法详解

  • 这是一个扩容方法,由图可见:新容量= 旧容量 * 2
    新负载= 新容量*扩容因子
    在这里插入图片描述
    其中最主要的就是transfer方法,他把旧table的值都给了newtable
    在这里插入图片描述
    用头插法插入新的hashTable因为是头插法,所以会导致链表反转
    元素迁移的过程中在多线程情境下有可能会触发死循环(无限进行链表反转)。
    出现环形链表的原因大概是这样的:线程1准备处理节点,线程二把HashMap扩容成功,链表已经逆向排序,那么线程1在处理节点时就可能出现环形链表。
    transfer方法详解
    在这里插入图片描述
    参考链接:1. https://zhuanlan.zhihu.com/p/114363420
    2. https://blog.csdn.net/F1004145107/article/details/105540106?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.pc_relevant_default&utm_relevant_index=2
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值