HashMap底层源码解析

数据结构

数组+单向链表+双向链表+红黑树

复制代码
    
数组:
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; }
复制代码

 

 

加载因子为0.75

static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted }

 

为什么Node下标的选择方式为“tab[i = (n - 1) & hash”

n:16

n-1:15

15:0000 1111

h:  0101 0101

& (1的时候为1)

------------------------

 0000  0101----》通过这种方式,hashcode是什么,算出来对应的下标是什么就是什么,而且不会越界(初始容量为16,下标永远是在0-15之间)。

从中我们可以引发一个思考,以上成立的条件初始容量必须要为2的幂次方,比如2、4、8、16、32、64等等,因为这种-1之后,低位才都是1。但是我们在创建HashMap的构造方法的时候,我们可以传入一个容量,那用户随便传入一个不是2的幂次方的数组,那么使用hashcode来定位下标不就失败了吗?其实,在内部的实现中,不会直接将这个数字定义为初始容量,他会进行判断,传入一个大于此数组的2的幂次方,比如传入10,他会赋值为16.

复制代码
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
      //初始化数组,目前数组为null,然后转至1.2
      //n表示数组长度,为16 n
= (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null)
       //当前Node数组下标没有数组,直接进行赋值 tab[i]
= newNode(hash, key, value, null); else {//当前下标已经存在node时,分为key相同和key不同,key不同,hashcode计算后的下标也有可能是相同的 Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p;//key为相同时 else if (p instanceof TreeNode)//红黑树 转至1.3 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//如果是树,需要将Node转换为Tree结构,进行红黑树的插入。转至1.4 else {//Node节点 for (int binCount = 0; ; ++binCount) {//for循环遍历,找到最后一个节点 if ((e = p.next) == null) {//1.7是使用头插法,1.8是使用尾插法--》使用尾插法可以在遍历的时候计算数量,因为数量达到8,转换成红黑树 p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) //因为上一步已经又插入了一个node,而binCount此时还是7,且binCount从0开始, treeifyBin(tab, hash); //变成红黑树是从Node节点有8个的时候开始装换,但是转换之前又插入了一个,一共节点有9个 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;//取出之前key对应的value值 if (!onlyIfAbsent || oldValue == null)//当已经存在key时,执行putIfAbsent()不会进行覆盖 e.value = value;//更新value值 afterNodeAccess(e); return oldValue;//当put覆盖之前的值时,put的返回值为之前key对应的value值 } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
复制代码

 

1.2 resize方法:初始化以及扩容。可以看到默认的初始容量为1<<4,初始容量等于2^4=16,扩容阈值为16*0.75;

复制代码
 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final float DEFAULT_LOAD_FACTOR = 0.75f;

final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold;//带容量的构造方法中 传入 int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&//此处是做扩容,左移一位,就是翻倍 oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; //传入大于此数字的2的幂次方数字 else { // zero initial threshold signifies using defaults
       //执行到此处
newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"})
      //newCap就是16 赋值了初始容量,并将newTab赋值了给table,此时Node完成了赋值 Node
<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) {//将老的数组中的数据转移到新的数组上 for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode)//当已有位置为树时 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order//当存在已有位置是链表时 Node<K,V> loHead = null, loTail = null;//转移新的数组,只会出现2中情况,1.还是在原来的位置,2.翻倍后的位置加1 Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
复制代码

  

 

1.3 可以看出TreeNode继承与Node。并且TreeNode有4个属性,left、right代表左右子树,prev、next(Node中的属性)代表双向链表

复制代码
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;
        TreeNode(int hash, K key, V val, Node<K,V> next) {
            super(hash, key, val, next);
        }

        /**
         * Returns root of tree containing this node.
         */
        final TreeNode<K,V> root() {
            for (TreeNode<K,V> r = this, p;;) {
                if ((p = r.parent) == null)
                    return r;
                r = p;
            }
        }
复制代码
    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }

 1.4 并不是Node节点链表达到8个就转换为红黑树,还需要数组长度达到64

复制代码
static final int MIN_TREEIFY_CAPACITY = 64;
final
void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); else if ((e = tab[index = (n - 1) & hash]) != null) { TreeNode<K,V> hd = null, tl = null; do { TreeNode<K,V> p = replacementTreeNode(e, null);//将Node节点装换成TreeNode节点 if (tl == null) hd = p; else { p.prev = tl;//指向前一个TreeNode节点,此时是双向链表 tl.next = p; } tl = p; } while ((e = e.next) != null); if ((tab[index] = hd) != null) hd.treeify(tab); } }
复制代码

 

 

 

 

===============================================================================================

 

LinkedHashMap

 

HashMap是不能保证迭代器的顺序。

他继承了HashMap

 

posted @   WXY_WXY  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示