HashMap源码笔记

hashmap查找 getNode()

  1. 计算hash key.hashCode() ^ (h>>>16) 高位参与运算增加hash的复杂度
  2. 先定位健在数组中位置->桶
    tab[hash & (n-1)] 相当于模运算
  3. 如果桶是空的,直接返回null
  4. 如果桶中第一个节点满足 first.hash == hash && ((k = first.key) == key || key.equals(k))
    关于第一个比较 first.hash == hash  比较的是hashcode。如果相同值: a.equals(b)==true的两个对象,hashcode不等,则会造成两个相同值的对象插入。
    所以需要重写hashCode 是的值相同的对象,hashCode相同。
    

如果不等,在链表中查找或者在红黑树中查找。

插入过程

  1. 如果没有初始化,先扩容。

  2. 定位桶的位置

    1. 如果桶首元素为空,则直接插入。
    2. 如果桶首元素就匹配到,直接改写value
    3. 如果未匹配到,往下。
    4. 如果是TreeNode,则调用红黑树插入方法。putTreeVal
    5. 否则是链表,当不包含key相同的键值对,直接插到链表尾部,然后判断是否需要树化。
    6. 否则存在于hashmap,直接更新值(onlyIfAbsent if true, don't change existing value)

    modcount++

    1. 判断是否超过扩容阈值,超过扩容

扩容过程

  1. hashmap 之前就初始化过
    newcap 设置为原来的两倍

    若( defualt <= oldcap < max && newcap < max)
    newThr = oldThr <<1;

  2. 未初始化过

    1. 初始化过oldThr
      newCap = oldThr
    2. 未初始化过 newThr
      初始化 cap = init
      初始化 thr = capfactor
      若 newThr == 0
      newThr == newcap
      factor

扩容开始( oldTab!=null)
新建一个新数组

遍历旧数组

对每个桶采取以下策略:

  1. 当桶内只有单个元素 ,该单个元素的hash值与(newcap-1)相与,找到新位置。放入。
  2. 当桶内是树
  3. 当桶内是链表 , 将链表拆分成低位高位两个链表。

我们使用的是2次幂的扩展(指长度扩为原来2倍)。定位数组的掩码增加了一位。所以根据节点在增加的那位上是1/0,可以判断出元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。

根据这个将链表拆分为高位合和低位。

最后放入新的桶

树化过程 treeifybin

  1. 比较 min_treefy_capacity = 64 length < min => 先扩容
  2. 将普通节点转化为树形节点,组织成保留原来次序的双向链表。确定树根。
  3. 树化 treeify
    1. 在遍历树形节点链表的循环中进行插入操作。
    2. 如果没有根节点,确定根节点。根节点设置为黑色。
    3. 插入节点必须是红色。如果有根节点,或则说有了一颗树。用当前链表节点和树节点作compare,确定是在树节点的左子树还是右子树。整个过程类似二叉搜索树的插入。
    4. 其中要注意的是这个compare过程。由于不知道key有没有实现compare接口,所以用3步比较法来比较。
        + 直接比较hash值,链表节点小于树节点,应该放在树节点左子树,反之放在右边。
        + 如果hash值相等,判断key是否实现compareble接口,实现了直接用compareComparables比较。
        + 没实现 用 tieBreakOrder 仲裁 。先比较类型是否相同,再调用identityHashCode 进行原生hash比较。返回-1和1 
    

树化之后仍使用next保留次序。

红黑树的拆分

由于在树化的过程中,保留了next属性,所以我们仍然可以用过next引用像遍历链表一样遍历红黑树。

如此就可以像处理链表一样,将红黑树拆分成高位链表和低位链表。

如果子链表长度小于等于6,则需要链化操作。在这个过程中,需要将树形节点转化为链表节点。

否则,进行重新树化。

你可能感兴趣的:(HashMap源码笔记)