Skip to the content.

王孝东的个人空间

HashMap源码分析

HashMap.java

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    
    // HashMap的默认容量:table数组的默认长度
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    // table的最大容量,该容量表示table数组的长度,并不是装入的元素个数,因为一个位置上可以有多个元素(Node),每个Node的next属性会保存其后面的Node的地址;因此一个索引位置上可能不止一个Node
    static final int MAXIMUM_CAPACITY = 1 << 30;

    // 默认加载因子;如果没有设置加载因子时,使用该值 
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * The bin count threshold for using a tree rather than list for a
     * bin.  Bins are converted to trees when adding an element to a
     * bin with at least this many nodes. The value must be greater
     * than 2 and should be at least 8 to mesh with assumptions in
     * tree removal about conversion back to plain bins upon
     * shrinkage.
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * The bin count threshold for untreeifying a (split) bin during a
     * resize operation. Should be less than TREEIFY_THRESHOLD, and at
     * most 6 to mesh with shrinkage detection under removal.
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * The smallest table capacity for which bins may be treeified.
     * (Otherwise the table is resized if too many nodes in a bin.)
     * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
     * between resizing and treeification thresholds.
     */
    static final int MIN_TREEIFY_CAPACITY = 64;
    
    
    /* ---------------- Fields -------------- */
    
    // 存储所有键值对的数组,所有的键值对被包装成Node放入该数组中
    transient Node<K,V>[] table;

    /**
     * Holds cached entrySet(). Note that AbstractMap fields are used
     * for keySet() and values().
     */
    transient Set<Map.Entry<K,V>> entrySet;

    // Key-Value键值对的数量
    transient int size;

    // HashMap的结果修改次数,比如put, remove等方法会每次增加该值
    transient int modCount;

    // 阈值: threshold = capacity * loadFactor; 当元素个数达到阈值时,HashMap调用resize()方法扩容,每次扩容都会将容量扩大为原来的两倍,扩容时会重新对原来的元素进行定位,并将新的索引位置指向原来的元素
    int threshold;

    // 加载因子:用于设置HashMap的扩容阈值threshold; 表示当Node个数达到多满的程度时,会扩容HashMap
    // 影响HashMap的两个因素:初始容量(initialCapacity)和加载因子(loadFactor)。初始容量是构造方法中直接指定,如果你能大概估计到你的HashMap需要存多少数据,可以在new的时候会其指定初始容量,避免扩容操作
    // 默认的加载因子为0.75;这是一个空间和时间的一个好的平衡点。如果加载因子过小:如0.1;这样会导致只有1/10的index被使用时就会扩容,也就是9/10的索引位会留空,这样会浪费空间;但是你的Node(每个key,value会包装成Node存储在able中)就不容易重复出现在某个索引位上
    // 如果加载因子过大:比如设置为10,这样会导致你某个索引位上会出现多个Node,获取Node时就会造成多次比较。比如初始容量为16,加载因子为10; 就会出现160个Node存放在在16个索引位置上,平均每个都有10个Node,get(key)时就会再比较元素的key,而不能直接通过hash值取出元素
    final float loadFactor;
    
    // 使用key的hashCode生成新的hash值,因为hashCode方法可以被任意重写,为了避免有不好的hashCode()实现,所以需要将hashCode重新hash:
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
    /**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>.)
     */
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        
        tab = table;
        n = tab.lenght;
        
        // 1. 首先查看table是否为空(table是存储节点的数组)
        if (tab == null || n == 0){
            n = (tab = resize()).length;
        }
        
        // 通过位与操作获取该hash值对应的index,位与运算保证结果肯定小于(length-1)
        int index = (n-1) & hash;
        p = tab[index];  //取得该索引位置上的第一个节点(数组索引只能取到第一个Node,(如果还有)后续的Node只能通过Node的next属性依次访问)
        
        if (p == null) {    // 如果该位置上的元素为空,则说明之前没有任何元素放入到该位置上,该元素(p)作为该位置上的第一个node
            tab[index] = newNode(hash, key, value, null);
        } else {    // 不为空,说明之前已经有元素存入该索引位置;(即两个元素的hash值与length-1的位与运行得到同一个index值)
            Node<K,V> e; K k;
            // 判断:当前的Key的hash值和之前的取出的相同位置上的第一个节点p的hash值相等,且他们的key也是相同的;如果为true,则表示Key是同一个对象
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) {     
                e = p;  // Key对象已存在,条件成立;将原来的Node接待赋值给e,后续会对e进行判断。
            } else if (p instanceof TreeNode) {     // 用于TreeMap的元素判断?? TODO
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            } else {
                // 遍历该索引位置上的Node链表;for循环中没有退出条件,下面的条件满足时会主动break;
                // 遍历只会出现以下两种情况中的一种,即要么已存在一个key与当前key对象相等,此时用新的value替换原来的value;
                // 要么没有任何key与当前key对象相等,则将新的key对象放入该链表的最后一个Node
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {     // 判断p.next是否为空
                        // 如果为空,说明没有Node需要继续遍历比较;走到此步,说明没有重复的Key被找到。
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) { // -1 for 1st
                            treeifyBin(tab, hash);
                        }
                        break;      // 将新的键值对放到p.next之后直接跳出循环
                    }
                    
                    // 判断p.next的key对象是否为当前的key对象,如果是,直接跳出循环,后面的if会为原来的Node赋予新的value值
                    if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
                        break;
                    }
                    
                    p = e;  // 执行到此处,说明前面的条件都为false,将e赋值给p(也就是p=p.next),然后继续执行下一次for循环。
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;    // 将新的value赋值给已存在的Node
                afterNodeAccess(e);     // 用于TreeMap,HashMap得该方法为空实现。
                return oldValue;       // 直接返回原始的value, 不需要执行后面的操作,因为只是替换了value值,结构没变,也不用判断判断是否需要扩容 
            }
        }
        ++modCount; // 执行到次数,说明没有已存在的key对象,需要添加新的键值对到table中,因此需要增加modCount(操作次数)
        if (++size > threshold)     //检查数量是否达到阈值
            resize();       // 扩容
        afterNodeInsertion(evict);      // for TreeMap
        return null;
    }
    
    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

    /**
     * Implements Map.get and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }
}

hashCode和euqals方法对应HashMap来说是非常重要,添加元素时通常都需要使用hashCode和equals方法来判断键值对的index以及是否已存在当前的key对象;hashCode如此重要,可以参加另一篇关于hashCode的文章

下面是用于测试HashMap中的hash函数以及观察hash分布情况的示例代码:

public class MapTest<K, V> {

    // 该方法和HashMap中的hash方法完全一致;只是HashMap中的hash方法没有公开出来,不能直接调用,所以参照源码写了一个一样的,用于观察每步的执行结果
    public static int hash(Object key){
        int tableLength = 16;
        int h;
        if(key == null ){
            return 0;
        } else {
            // 通过对象的默认hashCode得到HashMap中的hash值,并查看index分布情况
            h = key.hashCode();
            out.println("h = hashCode:         " + String.format("%1$32s",Integer.toBinaryString(h)).replace(' ','0'));
            out.println("h/16 = hashCode>>>16: " + String.format("%1$32s",Integer.toBinaryString(h>>>16)).replace(' ','0'));
            int hash = h ^ (h >>> 16);
            out.println("hash = h ^ h/16:      " + String.format("%1$32s",Integer.toBinaryString(hash)).replace(' ','0'));
            out.println("last=length-1:        " + String.format("%1$32s",Integer.toBinaryString((tableLength-1))).replace(' ','0'));
            out.println("index=last&hash:      " + String.format("%1$32s",Integer.toBinaryString((tableLength-1) & hash)).replace(' ','0'));
            out.println("index=last&hash:      " + ((tableLength-1) & hash));
            out.println("tableLength:          " + tableLength);
            out.println("hash:                 " + hash);
            out.println("key.toString:         " + key.toString());
            
            // 以下是将对象转换为String后,通过String的hashCode得到HashMap中的hash值,再查看index的分布情况
            // 因为String重写了hashCode方法,得到的hashCode并不是默认的hashCode(默认的hashCode为内存地址)
            int stringH = key.toString().hashCode();
            int stringHash = stringH ^ (stringH >>> 16);
            out.println("key.toString.hashCode:        " + String.format("%1$32s",Integer.toBinaryString(stringH)).replace(' ','0'));
            out.println("key.toString.hashCode>>>16:   " + String.format("%1$32s",Integer.toBinaryString(stringH>>>16)).replace(' ','0'));
            out.println("key.toString.stringHash:      " + String.format("%1$32s",Integer.toBinaryString(stringHash)).replace(' ','0'));
            out.println("last=length-1:                " + String.format("%1$32s",Integer.toBinaryString((tableLength-1))).replace(' ','0'));
            out.println("index=last&stringHash:        " + String.format("%1$32s",Integer.toBinaryString((tableLength-1) & stringHash)).replace(' ','0'));
            out.println("index=last&stringHash:        " + ((tableLength-1) & stringHash));
            out.println("key.toString.hashCode:        " + key.toString().hashCode());
            out.println("key.toString.stringHash:      " + stringHash );
            out.println("==========================================================");
            return hash;
        }
        //return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    public void testMap(){
        MapTest mapTest1 = new MapTest();
        MapTest mapTest2 = new MapTest();
        MapTest mapTest3 = new MapTest();
        MapTest mapTest4 = new MapTest();
        MapTest mapTest5 = new MapTest();
        MapTest mapTest6 = new MapTest();
        MapTest mapTest7 = new MapTest();
        MapTest mapTest8 = new MapTest();

        out.println("mapTest1.hash:" + hash(mapTest1));
        out.println("mapTest2.hash:" + hash(mapTest2));
        out.println("mapTest3.hash:" + hash(mapTest3));
        out.println("mapTest4.hash:" + hash(mapTest4));
        out.println("mapTest5.hash:" + hash(mapTest5));
        out.println("mapTest6.hash:" + hash(mapTest6));
        out.println("mapTest7.hash:" + hash(mapTest7));
        out.println("mapTest8.hash:" + hash(mapTest8));

        Map<MapTest, Integer> map = new HashMap<>(4,2);
        map.put(mapTest1,1);
        map.put(mapTest2,1);
        map.put(mapTest3,1);
        map.put(mapTest4,1);
        map.put(mapTest5,1);
        map.put(mapTest6,1);
        map.put(mapTest7,1);
        map.put(mapTest8,1);
        
        map.put(mapTest1,1);    // 测试重复的key

        out.println("map.size:" + map.size());
    }

    public static void main(String[] args){
        new MapTest().testMap();
    }
}

测试时,修改tableLength的值,以查看index的不同分布情况;我的结果如下:

h = hashCode:         00011011011011010011010110000110
h/16 = hashCode>>>16: 00000000000000000001101101101101
hash = h ^ h/16:      00011011011011010010111011101011
last=length-1:        00000000000000000000000000001000
index=last&hash:      00000000000000000000000000001000
index=last&hash:      8
tableLength:          9
hash:                 460140267
key.toString:         MapTest@1b6d3586
key.toString.hashCode:        00101011001110100000011001110001
key.toString.hashCode>>>16:   00000000000000000010101100111010
key.toString.stringHash:      00101011001110100010110101001011
last=length-1:                00000000000000000000000000001000
index=last&stringHash:        00000000000000000000000000001000
index=last&stringHash:        8
key.toString.hashCode:        725223025
key.toString.stringHash:      725232971
==========================================================
mapTest1.hash:460140267
h = hashCode:         01000101010101000110000101111100
h/16 = hashCode>>>16: 00000000000000000100010101010100
hash = h ^ h/16:      01000101010101000010010000101000
last=length-1:        00000000000000000000000000001000
index=last&hash:      00000000000000000000000000001000
index=last&hash:      8
tableLength:          9
hash:                 1163142184
key.toString:         MapTest@4554617c
key.toString.hashCode:        00010010000011010010111111111001
key.toString.hashCode>>>16:   00000000000000000001001000001101
key.toString.stringHash:      00010010000011010011110111110100
last=length-1:                00000000000000000000000000001000
index=last&stringHash:        00000000000000000000000000000000
index=last&stringHash:        0
key.toString.hashCode:        302854137
key.toString.stringHash:      302857716
==========================================================
mapTest2.hash:1163142184
h = hashCode:         01110100101000010100010010000010
h/16 = hashCode>>>16: 00000000000000000111010010100001
hash = h ^ h/16:      01110100101000010011000000100011
last=length-1:        00000000000000000000000000001000
index=last&hash:      00000000000000000000000000000000
index=last&hash:      0
tableLength:          9
hash:                 1956720675
key.toString:         MapTest@74a14482
key.toString.hashCode:        01011111101101001001001010011001
key.toString.hashCode>>>16:   00000000000000000101111110110100
key.toString.stringHash:      01011111101101001100110100101101
last=length-1:                00000000000000000000000000001000
index=last&stringHash:        00000000000000000000000000001000
index=last&stringHash:        8
key.toString.hashCode:        1605669529
key.toString.stringHash:      1605684525
==========================================================
mapTest3.hash:1956720675
h = hashCode:         00010101010000001110000110011101
h/16 = hashCode>>>16: 00000000000000000001010101000000
hash = h ^ h/16:      00010101010000001111010011011101
last=length-1:        00000000000000000000000000001000
index=last&hash:      00000000000000000000000000001000
index=last&hash:      8
tableLength:          9
hash:                 356578525
key.toString:         MapTest@1540e19d
key.toString.hashCode:        11011000100100011101000001101001
key.toString.hashCode>>>16:   00000000000000001101100010010001
key.toString.stringHash:      11011000100100010000100011111000
last=length-1:                00000000000000000000000000001000
index=last&stringHash:        00000000000000000000000000001000
index=last&stringHash:        8
key.toString.hashCode:        -661532567
key.toString.stringHash:      -661583624
==========================================================
mapTest4.hash:356578525
h = hashCode:         01100111011100110010011110110110
h/16 = hashCode>>>16: 00000000000000000110011101110011
hash = h ^ h/16:      01100111011100110100000011000101
last=length-1:        00000000000000000000000000001000
index=last&hash:      00000000000000000000000000000000
index=last&hash:      0
tableLength:          9
hash:                 1735606469
key.toString:         MapTest@677327b6
key.toString.hashCode:        01001110111101011110010000001000
key.toString.hashCode>>>16:   00000000000000000100111011110101
key.toString.stringHash:      01001110111101011010101011111101
last=length-1:                00000000000000000000000000001000
index=last&stringHash:        00000000000000000000000000001000
index=last&stringHash:        8
key.toString.hashCode:        1324737544
key.toString.stringHash:      1324722941
==========================================================
mapTest5.hash:1735606469
h = hashCode:         00000001010010101110010110100101
h/16 = hashCode>>>16: 00000000000000000000000101001010
hash = h ^ h/16:      00000001010010101110010011101111
last=length-1:        00000000000000000000000000001000
index=last&hash:      00000000000000000000000000001000
index=last&hash:      8
tableLength:          9
hash:                 21685487
key.toString:         MapTest@14ae5a5
key.toString.hashCode:        10011100011111100100110110110000
key.toString.hashCode>>>16:   00000000000000001001110001111110
key.toString.stringHash:      10011100011111101101000111001110
last=length-1:                00000000000000000000000000001000
index=last&stringHash:        00000000000000000000000000001000
index=last&stringHash:        8
key.toString.hashCode:        -1669444176
key.toString.stringHash:      -1669410354
==========================================================
mapTest6.hash:21685487
h = hashCode:         01111111001100010010010001011010
h/16 = hashCode>>>16: 00000000000000000111111100110001
hash = h ^ h/16:      01111111001100010101101101101011
last=length-1:        00000000000000000000000000001000
index=last&hash:      00000000000000000000000000001000
index=last&hash:      8
tableLength:          9
hash:                 2133941099
key.toString:         MapTest@7f31245a
key.toString.hashCode:        01100110001011000100111111001101
key.toString.hashCode>>>16:   00000000000000000110011000101100
key.toString.stringHash:      01100110001011000010100111100001
last=length-1:                00000000000000000000000000001000
index=last&stringHash:        00000000000000000000000000000000
index=last&stringHash:        0
key.toString.hashCode:        1714180045
key.toString.stringHash:      1714170337
==========================================================
mapTest7.hash:2133941099
h = hashCode:         01101101011011110110111000101000
h/16 = hashCode>>>16: 00000000000000000110110101101111
hash = h ^ h/16:      01101101011011110000001101000111
last=length-1:        00000000000000000000000000001000
index=last&hash:      00000000000000000000000000000000
index=last&hash:      0
tableLength:          9
hash:                 1835991879
key.toString:         MapTest@6d6f6e28
key.toString.hashCode:        10011100100010101001110011100101
key.toString.hashCode>>>16:   00000000000000001001110010001010
key.toString.stringHash:      10011100100010100000000001101111
last=length-1:                00000000000000000000000000001000
index=last&stringHash:        00000000000000000000000000001000
index=last&stringHash:        8
key.toString.hashCode:        -1668637467
key.toString.stringHash:      -1668677521
==========================================================
mapTest8.hash:1835991879
map.size:8

Process finished with exit code 0