再读HashMap源码

前言

今天被问了hashmap的源码,虽然之前看过,但是时间一长,而且当时没有做任何笔记,回答非常的模糊,所以决定回头再读一次。

jdk1.8 HashMap源码笔记

HashMap默认初始化大小为16

tatic final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 

最大容量为2^30

static final int MAXIMUM_CAPACITY = 1 << 30;

默认装载比率为75%,即达到容量的75%就会自动扩容

static final float DEFAULT_LOAD_FACTOR = 0.75f;

树形化阈值,众所周知,为了避免多个哈希值相等的元素都聚集在同一条链表中,导致查询效率降低,jdk1.8用红黑树代替了链表,但并不是完全代替,当某条链表的节点个数大于8时,会将链表转为红黑树

static final int TREEIFY_THRESHOLD = 8;

反之,当HashMap扩容是发现某条链表节点个数小于6时,会将红黑树还原为链表

static final int UNTREEIFY_THRESHOLD = 6;

当HashMap中的元素容量大于该值64时,上面所讲的树形化才会被执行

static final int MIN_TREEIFY_CAPACITY = 64;
下面记录一下重点的几个方法

resize()

//扩容方法
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) {
        	//首先检查旧的map里元素的个数是否超过最大容量
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }//如果没有超过且原有容量扩大一倍不大于最大容量
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; //新的容量为原来的2倍
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            //如果原容量为0且原map的可装载元素数大于0,则用原可装载数作为新map的最大容量
            newCap = oldThr;
        else {   
        //否则就使用默认值初始化一个新的map
            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;
        //这里初始化一个新容量的map
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
        	//循环遍历hash表
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                    	//判断该条链表如果只有一个值时,直接将该节点放入新hash表中
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                    //如果e属于红黑树,根据树中节点个数对树进行处理(还原成链表,或者保留树结构)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { 
                        Node<K,V> loHead = null, loTail = null;
                        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;
    }

上一个方法resize()中提到了根据某个桶内的元素个数对红黑树进行处理,用到的split()方法

final void split(HashMap map, Node[] tab, int index, int bit) {
     TreeNode b = this;
     // Relink into lo and hi lists, preserving order
     TreeNode loHead = null, loTail = null;
     TreeNode hiHead = null, hiTail = null;
     int lc = 0, hc = 0;
     for (TreeNode e = b, next; e != null; e = next) {
         next = (TreeNode)e.next;
         e.next = null;
         //如果当前节点哈希值的最后一位等于要修剪的 bit 值
         if ((e.hash & bit) == 0) {
                 //就把当前节点放到 lXXX 树中
             if ((e.prev = loTail) == null)
                 loHead = e;
             else
                 loTail.next = e;
             //然后 loTail 记录 e
             loTail = e;
             //记录 lXXX 树的节点数量
             ++lc;
         }
         else {  //如果当前节点哈希值最后一位不是要修剪的
                 //就把当前节点放到 hXXX 树中
             if ((e.prev = hiTail) == null)
                 hiHead = e;
             else
                 hiTail.next = e;
             hiTail = e;
             //记录 hXXX 树的节点数量
             ++hc;
         }
     }
     if (loHead != null) {
         //如果 lXXX 树的数量小于 6,就把 lXXX 树的枝枝叶叶都置为空,变成一个单节点
         //然后让这个桶中,要还原索引位置开始往后的结点都变成还原成链表的 lXXX 节点
         //这一段元素以后就是一个链表结构
         if (lc <= UNTREEIFY_THRESHOLD)
             tab[index] = loHead.untreeify(map);
         else {
         //否则让索引位置的结点指向 lXXX 树,这个树被修剪过,元素少了
             tab[index] = loHead;
             if (hiHead != null) // (else is already treeified)
                 loHead.treeify(tab);
         }
     }
     if (hiHead != null) {
         //同理,让 指定位置 index + bit 之后的元素
         //指向 hXXX 还原成链表或者修剪过的树
         if (hc <= UNTREEIFY_THRESHOLD)
             tab[index + bit] = hiHead.untreeify(map);
         else {
             tab[index + bit] = hiHead;
             if (loHead != null)
                 hiHead.treeify(tab);
         }
     }
 }

当链表元素个数大于阈值时,就会进行桶的树形化treeifyBin()

//将桶内所有的 链表节点 替换成 红黑树节点
final void treeifyBin(Node[] tab, int hash) {  
    int n, index; Node e;
    //如果当前哈希表为空,或者哈希表中元素的个数小于 进行树形化的阈值(默认为 64),就去新建/扩容
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        //如果哈希表中的元素个数超过了 树形化阈值,进行树形化
        // e 是哈希表中指定位置桶里的链表节点,从第一个开始
        TreeNode hd = null, tl = null; //红黑树的头、尾节点
        do {
            //新建一个树形节点,内容和当前链表节点 e 一致
            TreeNode p = replacementTreeNode(e, null);
            if (tl == null) //确定树头节点
                hd = p;
            else {
                p.prev = tl;
                tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);  
        //让桶的第一个元素指向新建的红黑树头结点,以后这个桶里的元素就是红黑树而不是链表了
        if ((tab[index] = hd) != null)
            hd.treeify(tab);
    }
}
 TreeNode replacementTreeNode(Node p, Node next) {
    return new TreeNode<>(p.hash, p.key, p.value, next);
}

上一个方法只是将链表转化为一个二叉树的结构,还没有但是我们发现,并没有设置红黑树的颜色值,现在得到的只能算是个二叉树。在 最后调用树形节点 hd.treeify(tab) 方法进行塑造红黑树

final void treeify(Node[] tab) {
        TreeNode root = null;
        for (TreeNode x = this, next; x != null; x = next) {
            next = (TreeNode)x.next;
            x.left = x.right = null;
            if (root == null) { //头回进入循环,确定头结点,为黑色
                x.parent = null;
                x.red = false;
                root = x;
            }
            else {  //后面进入循环走的逻辑,x 指向树中的某个节点
                K k = x.key;
                int h = x.hash;
                Class kc = null;
                //又一个循环,从根节点开始,遍历所有节点跟当前节点 x 比较,调整位置,有点像冒泡排序
                for (TreeNode p = root;;) {
                    int dir, ph;        //这个 dir 
                    K pk = p.key;
                    if ((ph = p.hash) > h)  //当比较节点的哈希值比 x 大时, dir 为 -1
                        dir = -1;
                    else if (ph < h)  //哈希值比 x 小时 dir 为 1
                        dir = 1;
                    else if ((kc == null &&
                              (kc = comparableClassFor(k)) == null) ||
                             (dir = compareComparables(kc, k, pk)) == 0)
                        // 如果比较节点的哈希值、 x 
                        dir = tieBreakOrder(k, pk);

                        //把 当前节点变成 x 的父亲
                        //如果当前比较节点的哈希值比 x 大,x 就是左孩子,否则 x 是右孩子 
                    TreeNode xp = p;
                    if ((p = (dir <= 0) ? p.left : p.right) == null) {
                        x.parent = xp;
                        if (dir <= 0)
                            xp.left = x;
                        else
                            xp.right = x;
                        root = balanceInsertion(root, x);
                        break;
                    }
                }
            }
        }
        moveRootToFront(tab, root);
    }

可以看到,将二叉树变为红黑树时,需要保证有序。这里有个双重循环,拿树中的所有节点和当前节点的哈希值进行对比(如果哈希值相等,就对比键,这里不用完全有序),然后根据比较结果确定在树种的位置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值