图解 HashMap 源码——逐行分析源码,面试再也不怕被问HashMap了

在上篇文章《HashMap、ConcurrentHashMap简单原理讲解》中,简单说了下HashMap的底层数据结构。

今天详细分析源码(JAVA 1.8)

一、HashMap 成员变量 与初始化

先说几个默认值


    /**
     * 默认初始容量16
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * 最大容量 2 的 30 次方
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 默认负载因子 0.75
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * 链表转红黑树的阈值 8(链表长度大于 8,可能转红黑树)
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * 红黑树转链表的阈值 6 (红黑树节点小于 6,红黑树退化为链表)
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * 当容量大于64,链表可转红黑树,容量小于64,优先扩容
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

HashMap 有一个内部类Node,key-value 的值就存在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 V setValue(V newValue) {
   
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
    }

在上一篇博客中,说到的数组,数组的元素就是 Node对象
在这里插入图片描述
下面是几个成员变量比较关键

    /**
     * 数组对象,用来存放Node
     */
    transient Node<K,V>[] table; 

    /**
     * 键值对,遍历时用
     */
    transient Set<Map.Entry<K,V>> entrySet;

    /**
     * HashMap 中实际存了多少个键值对
     */
    transient int size;

    /**
     * 修改次数(快速失败机制用)
     */
    transient int modCount;

    /**
     * 阈值(达到这个值时,就启动扩容),大小等于 容量*负载因子
     */
    int threshold;

    /**
     * 负载因子(前面说的那个 0.75是默认的值)
     */
    final float loadFactor;

初始化方法


    public HashMap(int initialCapacity) {
   
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }


    public HashMap() {
   
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
    

这里顺便说一下,如果在使用HashMap时,明确知道要放入多少个键值对。

那推荐用第一个初始化方法,即传入容量大小,这样操作过程中,可以避免扩容。

上面说 transient Node<K,V>[] table,这个数组大小,

它和 HashMap 中存入多少键值对,即参数 transient int size 不是严格对应的。

因为出现哈希冲突时,一个位置实际是存储了多个键值对。

table 可以近似的理解为,火车上有多少个座位;

size 则是实际上火车上有多少乘客。

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

这段是初始化最终调用的代码,这里并没有 new 一个数组出来。

这没什么意外的,数组要连续的内存空间,等用的时候,才会 new 一个数组过来。

这个等讲 扩容 的时候再详细说。

this.threshold = tableSizeFor(initialCapacity) 这段很重要。

先记住结论:tableSizeFor(initialCapacity),返回的是 2 的 n 次方


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

关于这段代码的解析,本篇不讲,可以看我以前的博客《2的n次方是怎么玩儿的》。

二、添加元素


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

put 方法中,先说 hash(key)

    static final int hash(Object key) {
   
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

key 的 hashCode 值为 h, h 高 16位 与 h 本身,做 异或运算

这个本身不难理解,据说这样做的好处:让高位也参与运算,可减少哈希冲突

hashMap 中采用的哈希函数是 hash & ( n-1 )

这里面 hash 就是上面那个 高16位参与的异或运算的结果, n 指的是数组的长度。

hash & ( n-1 ) 计算的结果,和 hash%n 计算的结果是一样的。

这个不懂没关系,记住就行,哈希函数就是计算数组下标的,

hash%n 得到的就是下标,这个好理解。在我之前的博文中,

详细说明了《为什么hash & ( n-1 ) = hash%n》。

我们接着看 put 方法。


    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)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
   
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值