大佬们, hashmap 的源码有个不明白的地方求助

2020-11-17 11:00:18 +08:00
 levizheng

请问一下。hashmap 在 putVal 的时候,为什么在当前的节点是红黑树的情况下,直接插入数据就可以,而不是像链表一样先循环遍历判断 key 是否相同呢?渣渣非科班小白不懂数据结构,求各位大佬赐教

if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }


2058 次点击
所在节点    Java
8 条回复
xuanbg
2020-11-17 11:28:56 +08:00
循环遍历判断 key 是否相同的话,还用什么 hash……
GM
2020-11-17 11:37:57 +08:00
循环遍历判断 key 是否相同的话,还用什么 hash…… +1
Joker123456789
2020-11-17 11:37:58 +08:00
你再看一下 putTreeVal 这个方法的源码呢。
aneureka
2020-11-17 11:39:54 +08:00
一楼不在 context 里 2333,你可以看看 TreeNode.putTreeVal 的方法实现,盲猜还是按红黑树搜索节点来做的(当然不同于简单循环遍历),有则替换,无则插入
Joker123456789
2020-11-17 11:43:33 +08:00
@GM
首先不同的对象,hashcode 可能会一样,这就导致了 你 put 两个不同的 key 可能 hashcode 一样,造成存到同一个下标里。 但是你明明 put 的是两个不同的 key,总不能直接覆盖吧,所以 才有了数组+链表的 数据结构,就是当 hash 碰撞时,在同一个下标里 把两个值存进去。 但是也不能直接追加吧,所以需要循环这个链表,判断 hasncode 和值是否都跟你 put 进来的这个 key 相等,如果相等就覆盖 value,不相等才追加。


现在 hashmap 做了优化,当一个下标里的链表过长时,会自动转成红黑树。
dasinigetudou
2020-11-17 11:47:06 +08:00
```
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,

​ int h, K k, V v) {

​ Class<?> kc = null;

​ boolean searched = false;

​ TreeNode<K,V> root = (parent != null) ? root() : this;

​ for (TreeNode<K,V> p = root;;) {

​ //树的根节点开始遍历

​ int dir, ph; K pk;

​ //比较根节点的 hash 值,dir 猜测是决定节点插入时应该插到左子节点还是右子节点

​ if ((ph = p.hash) > h)

​ dir = -1;

​ else if (ph < h)

​ dir = 1;

​ else if ((pk = p.key) == k || (k != null && k.equals(pk)))

​ //如果根节点的 key 和要插入节点的 key 相同,直接返回根节点

​ return p;

​ else if ((kc == null &&

​ (kc = comparableClassFor(k)) == null) ||

​ (dir = compareComparables(kc, k, pk)) == 0) {

​ //根节点的 key 和要插入的 key 不同,开始比较根节点的左右子节点

​ if (!searched) {

​ TreeNode<K,V> q, ch;

​ searched = true;

​ if (((ch = p.left) != null &&

​ (q = ch.find(h, k, kc)) != null) ||

​ ((ch = p.right) != null &&

​ (q = ch.find(h, k, kc)) != null))

​ //找到相同的 key,将节点返回

​ return q;

​ }

​ //这里记录下 dir,可能是决定为了如果从子节点也找不到接下来创建新的节点插入到左边还是右边

​ dir = tieBreakOrder(k, pk);

​ }



​ //到这里就是从红黑树找不到符合要求的节点了,创建新的节点,插入到红黑树

​ TreeNode<K,V> xp = p;

​ if ((p = (dir <= 0) ? p.left : p.right) == null) {

​ Node<K,V> xpn = xp.next;

​ TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);

​ if (dir <= 0)

​ xp.left = x;

​ else

​ xp.right = x;

​ xp.next = x;

​ x.parent = x.prev = xp;

​ if (xpn != null)

​ ((TreeNode<K,V>)xpn).prev = x;

​ moveRootToFront(tab, balanceInsertion(root, x));

​ return null;

​ }

​ }

​ }
```
不知道分析的对不对~希望大佬一起交流
dasinigetudou
2020-11-17 11:51:56 +08:00
```
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
int h, K k, V v) {
Class<?> kc = null;
boolean searched = false;
TreeNode<K,V> root = (parent != null) ? root() : this;
for (TreeNode<K,V> p = root;;) {
//树的根节点开始遍历
int dir, ph; K pk;
//比较根节点的 hash 值,dir 猜测是决定节点插入时应该插到左子节点还是右子节点
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
//如果根节点的 key 和要插入节点的 key 相同,直接返回根节点
return p;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
//根节点的 key 和要插入的 key 不同,开始比较根节点的左右子节点
if (!searched) {
TreeNode<K,V> q, ch;
searched = true;
if (((ch = p.left) != null &&
(q = ch.find(h, k, kc)) != null) ||
((ch = p.right) != null &&
(q = ch.find(h, k, kc)) != null))
//找到相同的 key,将节点返回
return q;
}
//这里记录下 dir,可能是决定为了如果从子节点也找不到接下来创建新的节点插入到左边还是右边
dir = tieBreakOrder(k, pk);
}

//到这里就是从红黑树找不到符合要求的节点了,创建新的节点,插入到红黑树
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
Node<K,V> xpn = xp.next;
TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
if (dir <= 0)
xp.left = x;
else
xp.right = x;
xp.next = x;
x.parent = x.prev = xp;
if (xpn != null)
((TreeNode<K,V>)xpn).prev = x;
moveRootToFront(tab, balanceInsertion(root, x));
return null;
}
}
}
```
levizheng
2020-11-17 13:25:34 +08:00
@Joker123456789
@aneureka
@dasinigetudou
感谢大佬们,果然是 putTreeVal 方法里进行了判断,之前看的不仔细了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/726094

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX