HashMap 重要源码分析(含代码、含图、多注释去帮助理解!!!)

236 阅读8分钟

HashMap 重要源码分析(含代码、含图、多注释去帮助理解!!!)

HashMap结构图.png

0. 简介

HashMap,

  1. 存放键值对,是非线程安全的。

  2. 可以存储 nullkeyvalue,但 null 作为键只能有一个,null 作为值可以有多个

  3. 由 数组+链表 组成的,但当链表长度超过阈值(默认为 8)会将链表转化为红黑树。(提高搜索效率)

  4. 默认初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。

  5. 总是使用 2 的幂作为哈希表的大小。

1. 类声明(public)

public class HashMap<K,V> extends AbstractMap<K,V>    implements Map<K,V>, Cloneable, Serializable {

2. 6 个常量属性(static final)

  1. 默认初始容量 1<<4 16
  2. 最大容量 1<<30 1073741824 ( 约 Integer.MAX_VALUE 的一半 )
  3. 默认装载因子 0.75f
  4. 转红黑树的阈值 8
  5. 转链表的阈值 6
  6. 转红黑树时数组所需的最小容量 64
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16static final int MAXIMUM_CAPACITY = 1 << 30;
​
static final float DEFAULT_LOAD_FACTOR = 0.75f;
​
static final int TREEIFY_THRESHOLD = 8;
​
static final int UNTREEIFY_THRESHOLD = 6;
​
static final int MIN_TREEIFY_CAPACITY = 64;

3. Node<K, V> 静态内部类(static)

1. 类定义

static class Node<K,V> implements Map.Entry<K,V> {}

2. 4 个属性

hash, key, value, next

3. 重要方法

int hashCode()

重写 Object 的 hashCode() 方法!使用 Objects 类的 hashCode() 方法分别求 key 和 value 的 hashCode,再相与(^)。

boolean equals(Object o)

重写 equals() 方法!

  1. 判断对象是否 = 相等
  2. 判断对象是否 instanceof Map.Entry
  3. 使用 Objects.equals() 判读是否 key 和 value 相同

源码

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;    
    }    
    // get() set() and toString() 省略          
    public final int hashCode() {        
        return Objects.hashCode(key) ^ Objects.hashCode(value);    
    }    
    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;    
    }
}

4. 静态方法(static)

int hash(Object key)

key.hashCode() ^ ( key.hashCode() >>> 16 )

// 根据key 进行 hash注意:使用的是 Object 的 hashCode() 方法

boolean comparableClassFor(Object x)

判断该对象能否比较

boolean compareComparables(...Object k, Object x)

通过 Comparable 的 compareTo判断两个对象是否相同

int tableSizeFor(int cap)

通过 >>> 和 ^ 运算,计算 >= cap 的 2 的幂返回

源码

static final int hash(Object key) {    
    int h;    
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
​
static Class<?> comparableClassFor(Object x) {    
    if (x instanceof Comparable) {        
        Class<?> c; 
        Type[] ts, as; Type t; ParameterizedType p;        
        if ((c = x.getClass()) == String.class) 
            // bypass checks            
            return c;        
        if ((ts = c.getGenericInterfaces()) != null) {            
            for (int i = 0; i < ts.length; ++i) {                
                if (((t = ts[i]) instanceof ParameterizedType) &&                    ((p = (ParameterizedType)t).getRawType() ==                     Comparable.class) &&                    (as = p.getActualTypeArguments()) != null &&                    as.length == 1 && as[0] == c) 
                    // type arg is c                    
                    return c;            
            }        
        }    
    }    
    return null;
}
​
@SuppressWarnings({"rawtypes","unchecked"}) 
// for cast to Comparablestatic 
int compareComparables(Class<?> kc, Object k, Object x) {    
    return (x == null || x.getClass() != kc ? 0 :            ((Comparable)k).compareTo(x));
}
​
static final int tableSizeFor(int cap) {    
    int n = cap - 1;    
    n |= n >>> 1;    
    n |= n >>> 2;    
    n |= n >>> 4;    
    n |= n >>> 8;    
    n |= n >>> 16;    
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

5. 6 个属性

  1. 表数组:transient Node<K, V>[] table
  2. 非空的桶的集合:transient Set<Map.Entry> set
  3. 表数组中非空桶的个数:transient int size
  4. 扩容阈值:int threshold(capacity * load factor)(未分配表数组时,此字段保存初始容量或为 0 )
  5. 修改次数:transient int modCount
  6. 装载因子:final float loadFactor
transient Node<K,V>[] table;
transient Set<Map.Entry<K,V>> entrySet;
transient int size;
transient int modCount;
int threshold;
final float loadFactor;

6. 4 个构造方法

  1. 有参,初始容量、装载因子
  2. 有参,初始容量(使用默认装载因子)
  3. 无参(使用默认装载因子)
  4. 有参,一个 Map(使用默认装载因子,调用 putMapEntries(m, false) 方法)

//省略,自行看源码

7. 方法

void putMapEntries(m, evict)

// 将传入的 map 中的元素存入当前 map 的 table 中

  • 判断 table 是否未分配,若未分配则求数值 t ,t 为在当前 loadFactor下足够存放 m 的元素而不至于扩容的数值(要求 <= MAXIMUM_CAPACITY )
  • 若 t > threshold 则将 threshold 设置为 >= t 的 2 的幂(注意:当前 table 未分配,threshold 保存的是初始容量或 0 )
  • 否则,若 m 的大小 > threshold 则 说明无论如何 HashMap 至少需要进行一次扩容的,因此调用 resize() 扩容一次
  • 遍历 m,将 m 的元素的 hash(key), key, value... 作为入参调用 putVal()
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);        
        }    
    }
}

V putVal(hash, key, value, onlyIfAbsent, evict)

p 为 (n-1)&hash 计算到的表数组的位置的引用

i 为定位到的表数组的下标

n 为表数组长度;

e 为 key:value 应写入的位置的引用(可能在表数组上、链表上或红黑树上);

onlyIfAbsent 不存在才 put key:value

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未分配则调用resize()分配 table
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
  
        //(n-1)&hash 算出表数组的下标i,p设为该位置的引用
        // 若 p==null 则在 i 位置 newNode存入该key:value
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        //若 table已分配且 p 已存有值
        else {
            Node<K,V> e; K k;
            //若 p 中存的key 和 需存入的key的hash以及key相等或equals,则 e=p
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //若p为红黑树节点则调用putTreeVal()确定e
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            //若p不为需写入的位置,则遍历链表找到e的位置
            else {
                for (int binCount = 0; ; ++binCount) {
                    //遍历到链表末尾未发现可替换的
                    if ((e = p.next) == null) {
                        //在链表末尾newNode,此时e=null
                        p.next = newNode(hash, key, value, null);
                        //若链表长度超过15,则调用treeifyBin()转红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //找到可替换的链表节点e
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key,若e!=null说明找到了可替换的节点
                V oldValue = e.value;
                //onlyIfAbsent=false 或者 oldValue=null 替换节点e的value
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        //程序走到这说明 原table中 没有可替换的 key:value
        //此时 修改次数+1 ,若hashmap的大小超过阈值则resize()
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

V put(key, value)

调用 putVal(key, value, false, false) 存元素

Node<K, V> getNode(hash, key)

first 为 key 在 table 上的映射的引用

e 为找到映射位置时遍历链表的引用,即用 e 遍历链表,类似与 while( e != null ){ e = e.next ; }

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    
    //首先,判断table已分配 且 table长度大于0 且 (n-1)&hash的位置有值
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        //first便是要找的 key:value则返回 value
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        //first.next!=null
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                //first若为树节点则调用getTreeNode()在红黑树上查找 key对应的value并返回
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            //遍历first链表直到找到 key 对应的value返回
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    //找不到对应的key
    return null;
}

V get(key)

调用 getNode(hash(key), key)

Node<K, V> resize()

  1. 若旧容量 > 0 (正常扩容)

  2. 若旧容量达到最大容量,将阈值设为 23112^{31}-1 ,返回旧 table

  3. 新容量为旧容量 2 倍,若新容量 < 最大容量 旧容量 >= 默认初始容量,新阈值为旧阈值 2 倍

  4. 若旧容量 <= 0 旧阈值 > 0(说明 table 未分配,旧阈值即为 HashMap 的初始化容量,此时需要通过 resize() 分配 table

  5. 新容量 = 旧阈值

  6. 若旧容量 <= 0 旧阈值 <= 0(说明 table 未分配,且初始化容量不合法)

  7. 使用新容量 = 默认的初始容量,新阈值 = 默认阈值

    final Node<K,V>[] resize() {
            Node<K,V>[] oldTab = table;
            int oldCap = (oldTab == null) ? 0 : oldTab.length;
            int oldThr = threshold;
            int newCap, newThr = 0;
            //oldCap,oldThr,newCap,newThr
            if (oldCap > 0) {  //oldCap>0,表示table已分配
                //oldCap达到最大容量则将threshold设定为int最大值,table将不再扩容
                if (oldCap >= MAXIMUM_CAPACITY) {
                    threshold = Integer.MAX_VALUE;
                    return oldTab;
                }
                //此时更新容量 newCap=2*oldCap
                //若新容量小于最大容量且oldCap达到了默认初始容量,newThr=2*oldThr
                else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                         oldCap >= DEFAULT_INITIAL_CAPACITY)
                    newThr = oldThr << 1; // double threshold
            }
            //oldCap<=0说明table未分配,oldThr此时为最初的初始化容量,若oldThr>0,则使用该初始化容量newCap=oldThr
            else if (oldThr > 0) // initial capacity was placed in threshold
                newCap = oldThr;
            //oldThr=0初始化容量为0,则使用HashMap默认的初始化容量和阈值
            else {               // zero initial threshold signifies using defaults
                newCap = DEFAULT_INITIAL_CAPACITY;
                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
            }
            //新阈值newThr未更新,说明新容量更新了但新容量大于最大容量 或 原容量未达到16;或者说明table未初始化但newCap=oldThr>0
            //ft=newCap * loadFactor
            if (newThr == 0) {
                float ft = (float)newCap * loadFactor;
                newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                          (int)ft : Integer.MAX_VALUE);
            }
            threshold = newThr;
            //new 新table,遍历旧table
            @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
            table = newTab;
            if (oldTab != null) {
                //遍历旧table
                for (int j = 0; j < oldCap; ++j) {
                    Node<K,V> e;
                    if ((e = oldTab[j]) != null) {
                        oldTab[j] = null;
                        //若当前位置元素没有next节点则直接rehash放入新table
                        if (e.next == null)
                            newTab[e.hash & (newCap - 1)] = e;
                        //若当前位置元素为树节点
                        else if (e instanceof TreeNode)
                            ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                        //若当前位置形成链表,将该链表分为low和high两条链表 分别 放入原下标j位置和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;
        }
    

void treeifyBin()

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

8. 内部类 TreeNode<K, V>

1. 类声明

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {

2. 5 个重要属性

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;

3. 方法

void treeify()

final void treeify(Node<K,V>[] tab) {
    TreeNode<K,V> root = null;
    //遍历树结点,x为当前遍历结点 next为下一结点
    for (TreeNode<K,V> x = this, next; x != null; x = next) {
        next = (TreeNode<K,V>)x.next;
        x.left = x.right = null;
        //若root结点为空,则当前结点x不为红且父结点为空,将当前结点设为root结点
        //此时当前结点x为root结点
        if (root == null) {
            x.parent = null;
            x.red = false;
            root = x;
        }
        //当前结点不为root结点时
        else {
            K k = x.key;
            int h = x.hash;
            Class<?> kc = null;
            //p=root
            //在for循环中 p为计划放入的结点
            for (TreeNode<K,V> p = root;;) {
                int dir, ph;
                K pk = p.key;
                //hash>上一个结点.hash: dir=-1
                if ((ph = p.hash) > h)
                    dir = -1;
                //hash<上一个结点.hash: dir=1
                else if (ph < h)
                    dir = 1;
                //key为没有实现Comparable接口 或 key可比较但比较发现 root.key和当前key相等
                //此时调用tieBreakOrder()方法进行比较,若仍比较为相等则判断为 上一个结点key > 当前key
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0)
                    dir = tieBreakOrder(k, pk);
​
                //此时已经判断好当前结点x 和 上一个结点结点 的key的大小
                //xp=root
                TreeNode<K,V> xp = p;
                //若当前结点x.key小则将x放入 上一个结点的左结点,否则放右结点,前提是准备放入的位置为空,否则(p指向准备放入的结点,继续遍历结点寻找位置放入
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    
                    x.parent = xp;
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                    //找到当前结点x可放入的位置并放入后,调用balenceInsertion()方法平衡红黑树
                    root = balanceInsertion(root, x);
                    break;
                }
            }
        }
    }
    moveRootToFront(tab, root);
}

\

HashMap结构图.png