hashmap添加元素的源码分析
//HashMap的主干数组,可以看到就是一个Node数组,初始值为空数组,主干数组的长度一定是2的次幂
transient Node<k,v>[] table;</k,v>
从源代码中可以看出:当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,
根据hash值得到这个元素在数组中的位置(即下标),
如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
如果数组该位置上已经存放有其他元素了,
根据哈希表中元素个数确定是扩容还是树形化
如果是扩容
那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。
如果是树形化
1.遍历桶中的元素,创建相同个数的树形节点,复制内容,建立起联系
2.然后让桶第一个元素指向新建的树头结点,替换桶的链表内容为树形内容
HashMap 中往红黑树中添加一个新节点 n 时,有以下操作:
1.从根节点开始遍历当前红黑树中的元素 p,对比 n 和 p 的哈希值;
2.如果哈希值不相等,根据当前节点与要添加的节点的哈希值的对比结果,确定往左还是往右添加
并且要保证当前节点还没有左孩子或者右孩子时,才能插入到相应位置
3.如果当前节点和要添加的节点的哈希值相等并且键也相等,就判断为已经有这个元素
(这里不清楚为什么不对比值--键不能重复);
4.如果当前节点和要添加的节点哈希值相等,但是两个节点的键不是一个类,只好去挨个对比左右孩子
5.如果从孩子所在子树中可以找到要添加的节点,就直接返回找到的节点,就判断为已经有这个元素
6.如果还是找不到,就通过其他信息进行比较,比如引用地址来给个大概比较结果,
这里可以看到红黑树的比较并不是很准确,注释里也说了,只是保证个相对平衡即可;
7.最后得到哈希值比较结果后,如果当前节点 p 还没有左孩子或者右孩子时才能插入,否则就进入下一轮循环;
8.插入元素后还需要进行红黑树例行的平衡调整,还有确保根节点的领先地位。
---------------------
static class Node<K,V> implements Map.Entry<K,V> { //哈希值,就是位置 final int hash; //键 final K key; //值 V value; //指向下一个几点的指针 Node<K,V> next; //... }
树节点:
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { TreeNode<K,V> parent; // red-black tree links TreeNode<K,V> left; TreeNode<K,V> right; TreeNode<K,V> prev; // needed to unlink next upon deletion boolean red; }
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K, V>[] tab; Node<K, V> p; int n, i; //判断table是否为空, if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length;//创建一个新的table数组,并且获取该数组的长度 //根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else {//如果对应的节点存在 Node<K, V> e; K k; //判断table[i]的首个元素是否和key一样,如果相同直接覆盖value if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; //判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对 else if (p instanceof TreeNode) e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value); else {// 该链为链表 //遍历table[i],判断链表长度是否大于TREEIFY_THRESHOLD(默认值为8),大于8的话把链表转换为红黑树, //在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可; 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; } } // 写入 if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; // 插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容 if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
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; if ((ph = p.hash) > h) dir = -1; else if (ph < h) dir = 1; else if ((pk = p.key) == k || (k != null && k.equals(pk))) //如果当前节点的哈希值、键和要添加的都一致,就返回当前节点(奇怪,不对比值吗?) return p; else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) { //如果当前节点和要添加的节点哈希值相等,但是两个节点的键不是一个类,只好去挨个对比左右孩子 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)) //如果从 ch 所在子树中可以找到要添加的节点,就直接返回 return q; } //哈希值相等,但键无法比较,只好通过特殊的方法给个结果 //这个方法用于 a 和 b 哈希值相同但是无法比较时,直接根据两个引用的地址进行比较 //这里源码注释也说了,这个树里不要求完全有序,只要插入时使用相同的规则保持平衡即可 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; } } }