码迷,mamicode.com
首页 > 编程语言 > 详细

【Java深入研究】9、HashMap源码解析(jdk 1.8)

时间:2018-03-21 19:57:06      阅读:228      评论:0      收藏:0      [点我收藏+]

标签:根据   imu   city   相同   重构   rev   article   string   awt   

一、HashMap概述

  HashMap是常用的Java集合之一,是基于哈希表的Map接口的实现。与HashTable主要区别为不支持同步和允许null作为key和value。由于HashMap不是线程安全的,如果想要线程安全,可以使用ConcurrentHashMap代替。

二、HashMap数据结构

  HashMap的底层是哈希数组,数组元素为Entry。HashMap通过key的hashCode来计算hash值,当hashCode相同时,通过“拉链法”解决冲突

  相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。原本Map.Entry接口的实现类Entry改名为了Node。转化为红黑树时改用另一种实现TreeNode。 

Node类

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;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

TreeNode类

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);
        }
    }

HashMap就是这样一个Entry(包括Node和TreeNode)数组,Node对象中包含键、值和hash值,next指向下一个Entry,用来处理哈希冲突。TreeNode对象包含指向父节点、子节点和前一个节点(移除对象时使用)的指针,以及表示红黑节点的boolean标识。

三、HashMap源码分析

1. 主要属性

    transient Node<K,V>[] table; // 哈希数组

    transient Set<Map.Entry<K,V>> entrySet; // entry缓存Set

    transient int size; // 元素个数

    transient int modCount; // 修改次数

    int threshold; // 阈值,等于加载因子*容量,当实际大小超过阈值则进行扩容

    final float loadFactor; // 加载因子,默认值为0.75

2. 构造方法

  以下是HashMap的几个构造方法。

 /**
     * 根据初始化容量和加载因子构建一个空的HashMap.
     */
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

    /**
     * 使用初始化容量和默认加载因子(0.75).
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * 使用默认初始化大小(16)和默认加载因子(0.75).
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

    /**
     * 用已有的Map构造一个新的HashMap.
     */
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }

3. 数据存取

putAll方法

public void putAll(Map<? extends K, ? extends V> m) {
        putMapEntries(m, true);
    }

    /**
     * Implements Map.putAll and Map constructor
     *
     * @param m the map
     * @param evict false when initially constructing this map, else
     * true (relayed to method afterNodeInsertion).
     */
    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        int s = m.size();
        if (s > 0) {
            if (table == null) { // pre-size
                float ft = ((float)s / loadFactor) + 1.0F;
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
                if (t > threshold)
                    threshold = tableSizeFor(t);
            }
            else if (s > threshold)
                resize();
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict); // put核心方法
            }
        }
    }

put方法

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

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) // table为空或length为0
            n = (tab = resize()).length; // 初始化
        if ((p = tab[i = (n - 1) & hash]) == null) // 如果hash所在位置为null,直接put
            tab[i] = newNode(hash, key, value, null);
        else { // tab[i]有元素,遍历节点后添加
            Node<K,V> e; K k;
            // 如果hash、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);
                        // 链表节点大于阈值8,调用treeifyBin方法,当tab.length大于64将链表改为红黑树
                        // 如果tab.length < 64或tab为null,则调用resize方法重构链表.
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    // hash、key都相等,此时节点即要更新节点
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            // 当前节点e = p.next不为null,表示链表中原本存在相同的key,则返回oldValue
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                // onlyIfAbsent值为false,参数主要决定存在相同key时是否执行替换
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold) // 检查是否超过阈值
            resize();
        afterNodeInsertion(evict);
        return null; // 原HashMap中不存在相同的key,插入键值对后返回null
    }
treeifyBin方法源码如下:
技术分享图片
 1     final void treeifyBin(Node<K,V>[] tab, int hash) {
 2         int n, index; Node<K,V> e;
 3         //当tab为null或tab.length<64需要进行扩容
 4         if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
 5             resize();
 6         else if ((e = tab[index = (n - 1) & hash]) != null) {
 7             TreeNode<K,V> hd = null, tl = null;
 8             do {
 9                 TreeNode<K,V> p = replacementTreeNode(e, null);
10                 if (tl == null)
11                     hd = p;
12                 else {
13                     p.prev = tl;
14                     tl.next = p;
15                 }
16                 tl = p;
17             } while ((e = e.next) != null);
18             if ((tab[index] = hd) != null)
19                 //存储在红黑树
20                 hd.treeify(tab);
21         }
22     }
技术分享图片

 

现在我们来看一下为什么需要扩容:

技术分享图片
 1     /**
 2      * The bin count threshold for using a tree rather than list for a
 3      * bin.  Bins are converted to trees when adding an element to a
 4      * bin with at least this many nodes. The value must be greater
 5      * than 2 and should be at least 8 to mesh with assumptions in
 6      * tree removal about conversion back to plain bins upon
 7      * shrinkage.
 8      */
 9     static final int TREEIFY_THRESHOLD = 8;
10 
11     /**
12      * The smallest table capacity for which bins may be treeified.
13      * (Otherwise the table is resized if too many nodes in a bin.)
14      * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
15      * between resizing and treeification thresholds.
16      */
17     static final int MIN_TREEIFY_CAPACITY = 64;
技术分享图片

 

MIN_TREEIFY_CAPACITY=64 > 4 * TREEIFY_THRESHOLD=4*8=32

如上图:转化红黑树的table容量最小容量64(JDK8定义的是64),至少是4*TREEIFY_THRESHOLD(单链表节点个数阀值,用以判断是否需要树形化)=32。用以避免 在扩容和树形结构化的阀值 可能产生的冲突。所以如果小于64必须要扩容。

 三、get查找

可见外部查找和JDK7差别不大,只是原本是entry[]查询,JDK8编程Node[]查询.都是 hash(key)找到table中元素位置,再遍历找到链表(或者是红黑树)元素的key。根据元素类型判断到底是查询哪个类型

技术分享图片
 1     public V get(Object key) {
 2         Node<K,V> e;
 3         return (e = getNode(hash(key), key)) == null ? null : e.value;
 4     }
 5 
 6     /**
 7      * Implements Map.get and related methods
 8      *
 9      * @param hash hash for key
10      * @param key the key
11      * @return the node, or null if none
12      */
13     final Node<K,V> getNode(int hash, Object key) {
14         Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
15         if ((tab = table) != null && (n = tab.length) > 0 &&
16             (first = tab[(n - 1) & hash]) != null) {
17             if (first.hash == hash && // always check first node
18                 ((k = first.key) == key || (key != null && key.equals(k))))
19                 return first;
20             if ((e = first.next) != null) {
21                 if (first instanceof TreeNode)
22                     return ((TreeNode<K,V>)first).getTreeNode(hash, key);
23                 do {
24                     if (e.hash == hash &&
25                         ((k = e.key) == key || (key != null && key.equals(k))))
26                         return e;
27                 } while ((e = e.next) != null);
28             }
29         }
30         return null;
31     }    
技术分享图片

 四、resize扩容

第二部put的时候,有可能需要resize。 因为newCap是oldCap的两倍所以原节点的索引值要么和原来一样,要么就是原(索引+oldCap)和JDK 1.7中实现不同这里不存在rehash

技术分享图片
 1 /**
 2      * Initializes or doubles table size.  If null, allocates in
 3      * accord with initial capacity target held in field threshold.
 4      * Otherwise, because we are using power-of-two expansion, the
 5      * elements from each bin must either stay at same index, or move
 6      * with a power of two offset in the new table.
 7      *
 8      * @return the table
 9      */
10     final Node<K,V>[] resize() {
11         Node<K,V>[] oldTab = table;
12         int oldCap = (oldTab == null) ? 0 : oldTab.length;
13         int oldThr = threshold;
14         int newCap, newThr = 0;
15         if (oldCap > 0) {
16             if (oldCap >= MAXIMUM_CAPACITY) {
17                 threshold = Integer.MAX_VALUE;
18                 return oldTab;
19             }
20             else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
21                      oldCap >= DEFAULT_INITIAL_CAPACITY)
22                 newThr = oldThr << 1; // double threshold
23         }
24         else if (oldThr > 0) // initial capacity was placed in threshold
25             newCap = oldThr;
26         else {               // zero initial threshold signifies using defaults
27             newCap = DEFAULT_INITIAL_CAPACITY;
28             newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
29         }
30         if (newThr == 0) {
31             float ft = (float)newCap * loadFactor;
32             newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
33                       (int)ft : Integer.MAX_VALUE);
34         }
35         threshold = newThr;
36         @SuppressWarnings({"rawtypes","unchecked"})
37             Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
38         table = newTab;
39         if (oldTab != null) {
40             for (int j = 0; j < oldCap; ++j) {
41                 Node<K,V> e;
42                 if ((e = oldTab[j]) != null) {
43                     oldTab[j] = null;
44                     if (e.next == null)
45                         newTab[e.hash & (newCap - 1)] = e;
46                     else if (e instanceof TreeNode)
47                         ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
48                     else { // preserve order
49                         Node<K,V> loHead = null, loTail = null;
50                         Node<K,V> hiHead = null, hiTail = null;
51                         Node<K,V> next;
52                         do {
53                             next = e.next;
54                             if ((e.hash & oldCap) == 0) {
55                                 if (loTail == null)
56                                     loHead = e;
57                                 else
58                                     loTail.next = e;
59                                 loTail = e;
60                             }
61                             else {
62                                 if (hiTail == null)
63                                     hiHead = e;
64                                 else
65                                     hiTail.next = e;
66                                 hiTail = e;
67                             }
68                         } while ((e = next) != null);
69                         if (loTail != null) {
70                             loTail.next = null;
71                             newTab[j] = loHead;
72                         }
73                         if (hiTail != null) {
74                             hiTail.next = null;
75                             //把节点移动新的位置j+oldCap,这种情况不适用与链表的节点数大于8的情况
76                             //链表节点大于8的情况会转换为红黑树存储
77                             newTab[j + oldCap] = hiHead;
78                         }
79                     }
80                 }
81             }
82         }
83         return newTab;
84     }
技术分享图片

五.HashMap节点红黑树存储

好了终于到treeify了,大部分内容都在注解中

技术分享图片
 1 final void treeify(Node<K,V>[] tab) {
 2             TreeNode<K,V> root = null;
 3             for (TreeNode<K,V> x = this, next; x != null; x = next) {
 4                 next = (TreeNode<K,V>)x.next;
 5                 x.left = x.right = null;
 6                 if (root == null) {
 7                     x.parent = null;
 8                     x.red = false;
 9                     root = x;
10                 }
11                 else {
12                     K k = x.key;
13                     int h = x.hash;
14                     Class<?> kc = null;
15                     //遍历root,把节点x插入到红黑树中,执行先插入,然后进行红黑树修正
16                     for (TreeNode<K,V> p = root;;) {
17                         int dir, ph;
18                         K pk = p.key;
19                         if ((ph = p.hash) > h)
20                             dir = -1;
21                         else if (ph < h)
22                             dir = 1;
23                         else if ((kc == null &&
24                                   (kc = comparableClassFor(k)) == null) ||
25                                  (dir = compareComparables(kc, k, pk)) == 0)
26                             dir = tieBreakOrder(k, pk);//比较k和pk的值,用于判断是遍历左子树还是右子树
27                         TreeNode<K,V> xp = p;
28                         if ((p = (dir <= 0) ? p.left : p.right) == null) {
29                             x.parent = xp;
30                             if (dir <= 0)
31                                 xp.left = x;
32                             else
33                                 xp.right = x;
34                             //修正红黑树
35                             root = balanceInsertion(root, x);
36                             //退出循环
37                             break;
38                         }
39                     }
40                 }
41             }
42             moveRootToFront(tab, root);
43         }
技术分享图片

上面主要做的是红黑树的insert,我们知道红黑树insert后是需要修复的,为了保持红黑树的平衡,我们来看下红黑树平衡的几条性质:
1.节点是红色或黑色。
2.根是黑色。
3.所有叶子都是黑色(叶子是NIL节点)。
4.每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
5.从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。

当insert一个节点之后为了达到平衡,我们可能需要对节点进行旋转和颜色翻转(上面的balanceInsertion方法)。

技术分享图片
 1 static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
 2                                                     TreeNode<K,V> x) {
 3             //插入的节点必须是红色的,除非是根节点                                            
 4             x.red = true;
 5             //遍历到x节点为黑色,整个过程是一个上滤的过程
 6             //xp=x.parent;xpp=xp.parent;xppl=xpp.left;xppr=xpp.right;
 7             for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
 8                 if ((xp = x.parent) == null) {
 9                     x.red = false;
10                     return x;
11                 }
12                 //如果xp的黑色就直接完成,最简单的情况
13                 else if (!xp.red || (xpp = xp.parent) == null)
14                     return root;
15                 //如果x的父节点是x父节点的左节点
16                 if (xp == (xppl = xpp.left)) {
17                     //x的父亲节点的兄弟是红色的(需要颜色翻转)
18                     if ((xppr = xpp.right) != null && xppr.red) {
19                         //x父亲节点的兄弟节点置成黑色
20                         xppr.red = false;
21                         //父几点和其兄弟节点一样是黑色
22                         xp.red = false;
23                         //祖父节点置成红色
24                         xpp.red = true;
25                         //然后上滤(就是不断的重复上面的操作)
26                         x = xpp;
27                     }
28                     else {
29                         //如果x是xp的右节点整个要进行两次旋转,先左旋转再右旋转
30                         if (x == xp.right) {
31                             root = rotateLeft(root, x = xp);
32                             xpp = (xp = x.parent) == null ? null : xp.parent;
33                         }
34                         if (xp != null) {
35                             xp.red = false;
36                             if (xpp != null) {
37                                 xpp.red = true;
38                                 root = rotateRight(root, xpp);
39                             }
40                         }
41                     }
42                 }
43                 //以左节点镜像对称就不做具体分析了 
44                 else {
45                     if (xppl != null && xppl.red) {
46                         xppl.red = false;
47                         xp.red = false;
48                         xpp.red = true;
49                         x = xpp;
50                     }
51                     else {
52                         if (x == xp.left) {
53                             root = rotateRight(root, x = xp);
54                             xpp = (xp = x.parent) == null ? null : xp.parent;
55                         }
56                         if (xp != null) {
57                             xp.red = false;
58                             if (xpp != null) {
59                                 xpp.red = true;
60                                 root = rotateLeft(root, xpp);
61                             }
62                         }
63                     }
64                 }
65             }
66         }
技术分享图片

出处:

https://www.cnblogs.com/dennyzhangdd/p/6745282.html

http://blog.csdn.net/u014026363/article/details/56342142

【Java深入研究】9、HashMap源码解析(jdk 1.8)

标签:根据   imu   city   相同   重构   rev   article   string   awt   

原文地址:https://www.cnblogs.com/wangzhongqiu/p/8618771.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!