hashmap查找 getNode()
- 计算hash key.hashCode() ^ (h>>>16) 高位参与运算增加hash的复杂度
- 先定位健在数组中位置->桶
tab[hash & (n-1)] 相当于模运算 - 如果桶是空的,直接返回null
- 如果桶中第一个节点满足
first.hash == hash && ((k = first.key) == key || key.equals(k))
关于第一个比较 first.hash == hash 比较的是hashcode。如果相同值: a.equals(b)==true的两个对象,hashcode不等,则会造成两个相同值的对象插入。 所以需要重写hashCode 是的值相同的对象,hashCode相同。
如果不等,在链表中查找或者在红黑树中查找。
插入过程
-
如果没有初始化,先扩容。
-
定位桶的位置
- 如果桶首元素为空,则直接插入。
- 如果桶首元素就匹配到,直接改写value
- 如果未匹配到,往下。
- 如果是TreeNode,则调用红黑树插入方法。putTreeVal
- 否则是链表,当不包含key相同的键值对,直接插到链表尾部,然后判断是否需要树化。
- 否则存在于hashmap,直接更新值(onlyIfAbsent if true, don't change existing value)
modcount++
- 判断是否超过扩容阈值,超过扩容
扩容过程
-
hashmap 之前就初始化过
newcap 设置为原来的两倍若( defualt <= oldcap < max && newcap < max)
newThr = oldThr <<1; -
未初始化过
- 初始化过oldThr
newCap = oldThr - 未初始化过 newThr
初始化 cap = init
初始化 thr = capfactor
若 newThr == 0
newThr == newcap factor
- 初始化过oldThr
扩容开始( oldTab!=null)
新建一个新数组
遍历旧数组
对每个桶采取以下策略:
- 当桶内只有单个元素 ,该单个元素的hash值与(newcap-1)相与,找到新位置。放入。
- 当桶内是树
- 当桶内是链表 , 将链表拆分成低位高位两个链表。
我们使用的是2次幂的扩展(指长度扩为原来2倍)。定位数组的掩码增加了一位。所以根据节点在增加的那位上是1/0,可以判断出元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。
根据这个将链表拆分为高位合和低位。
最后放入新的桶
树化过程 treeifybin
- 比较 min_treefy_capacity = 64 length < min => 先扩容
- 将普通节点转化为树形节点,组织成保留原来次序的双向链表。确定树根。
- 树化 treeify
1. 在遍历树形节点链表的循环中进行插入操作。 2. 如果没有根节点,确定根节点。根节点设置为黑色。 3. 插入节点必须是红色。如果有根节点,或则说有了一颗树。用当前链表节点和树节点作compare,确定是在树节点的左子树还是右子树。整个过程类似二叉搜索树的插入。 4. 其中要注意的是这个compare过程。由于不知道key有没有实现compare接口,所以用3步比较法来比较。 + 直接比较hash值,链表节点小于树节点,应该放在树节点左子树,反之放在右边。 + 如果hash值相等,判断key是否实现compareble接口,实现了直接用compareComparables比较。 + 没实现 用 tieBreakOrder 仲裁 。先比较类型是否相同,再调用identityHashCode 进行原生hash比较。返回-1和1
树化之后仍使用next保留次序。
红黑树的拆分
由于在树化的过程中,保留了next属性,所以我们仍然可以用过next引用像遍历链表一样遍历红黑树。
如此就可以像处理链表一样,将红黑树拆分成高位链表和低位链表。
如果子链表长度小于等于6,则需要链化操作。在这个过程中,需要将树形节点转化为链表节点。
否则,进行重新树化。
网友评论