Java8 HashMap源码解析

Java8 HashMap

Java8 在 Java7 的基础上对 HashMap 进行优化,由数组+链表结构,改为了数组+链表+红黑树结构组成。

查找的时候,根据 hash 值我们能够快速定位到数组的具体下标,但是之后的话,需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度,为 O(n)。为了降低这部分的开销,在 Java8 中,当链表中的元素达到了 8 个时,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。

Java8 HashMap 结构

image

put过程

image
image

put 过程

1、在第一进入会触发resize()进行一次扩容操作

2、根据位置判断该位置是否有参数,如果没有参数则将创建一个新结点

3、否则,进入查询,首先,判断该位置的第一个数据和我们要插入的数据,key 是不是"相等",如果是,取出这个节点

4、如果该节点是代表红黑树的节点,调用红黑树的插值方法

5、如果该节点不是红黑树,则表示是链表结构,则插入到最后的位置

6、TREEIFY_THRESHOLD 为 8,所以,如果新插入的值是链表中的第 8 个,会触发下面的 treeifyBin,也就是将链表转换为红黑树

7、如果在该链表中找到了"相等"的 key(== 或 equals),此时 break,那么 e 为链表中[与要插入的新值的 key "相等"]的 node

8、e!=null 说明存在旧值的key与要插入的key"相等",做 value 覆盖操作

9、如果 HashMap 由于新插入这个值导致 size 已经超过了阈值,需要进行扩容

/**
 * Tree version of putVal.
 */
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)
                )
                    return q;
            }
            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;
    }
    }
}

红黑树插入:

1、 红黑树初始为空树

2、插入结点,设置结点为黑结点,并设置为根结点

3、插入结点的父结点为黑结点,则可以直接插入

4、插入结点的父结点为红结点,插入结点的父结点是祖父结点的左子结点,且叔叔结点为空或者为黑结点

5、如果该结点是父结点的左子结点,进行右旋操作,并将父结点变黑,祖父结点变红

6、如果该结点是父结点的右子结点,进行左旋操作,并将父结点变黑,祖父结点变红

7、插入结点的父结点为红结点,插入结点的父结点是祖父结点的右子结点,且叔叔结点为空或者为黑结点

8、如果该结点是父结点的右子结点,进行左旋操作,并将父结点变黑,祖父结点变红

9、如果该结点是父结点的左子结点,进行右旋操作,并将父结点变黑,祖父结点变红

get过程

image
image

相对于 put 过程,get 过程就要简单很多:

1、通过 hash 值,确定在数组中的位置

2、判断数组中取到结点是否要取的结点,如果是则直接进行返回;如果不是则进行链表或红黑树结点查询

3、进行判断该位置使用数据结构是链表结构还是树形结构

4、如果是树结点,查询树结点并将数据返回

5、如果是链表结构,进入循环体,根据 hash值 与 key 相匹配获取结点对象并返回

resize 过程


/**
 * Initializes or doubles table size.  If null, allocates in
 * accord with initial capacity target held in field threshold.
 * Otherwise, because we are using power-of-two expansion, the
 * elements from each bin must either stay at same index, or move
 * with a power of two offset in the new table.
 *
 * @return the table
 */
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;
    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" })
    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;
                    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过程

1、如果hash表容量已达到最大临界值,则返回原数组,并且扩容临界值保持不变,否则,数组扩容一倍且扩容后的表不能大于限制值,将扩容临界值(该临界值还未乘以加载因子)翻倍

2、第一次创建,使用默认值生成相关参数

3、第一次扩容初始化阈值

4、更新扩容临界值

5、遍历数组中每个结点

6、如果取出来的结点不存在下一个元素,则重新计算对应新结点的位置

7、判断当前节点的hash值的比hash表容量高一位的二进制位是否为1,如果为0,则结点保持原位,如果为1,到新的结点位置

8、原位置的链表尾,将当前结点设置为链表头

9、当前前点追加到尾结点的下一结点

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容