HashMap源码解析

时间:2019-11-23
本文章向大家介绍HashMap源码解析,主要包括HashMap源码解析使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11918123.html

开始!

关于HashMap平时用的多, 面试的时候问的也多, 会问Hash碰撞, Hash表扩容, Hash表的整体实现数据结构, 自己用的时候也会遇到一些问题, 用多个线程去处理HashMap时会发生一点奇怪的东西, 有时候是百思不得其解, 不过确也能知其一二, 之前也看过几遍源码, 但是也是过三两天就忘得差不多了, 现记录下, 便日后方便反复阅读理解.

JDK版本:1.8

HashMap数据结构 : 数组 + 链表 + 红黑树 (JDk1.8)

首先来一张自制的文本数据结构原理图:

 略有粗糙, 不过也能看.

JDk1.8的hashmap由数组+链表+红黑树组成, 数组中的一个元素叫做bucket(桶), 一个桶里面可以有多个节点,桶可由链表或者红黑树组成. 关于何时使用链表, 何时使用红黑树在下面的属性中会有说明.

 首先介绍一下各个属性值, 有默认的, 也有后期需要使用到的.

★ 红黑树有关的三个值

/**
 * 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;

 其他一些字段介绍:

/**
 * The table, initialized on first use, and resized as
 * necessary. When allocated, length is always a power of two.
 * (We also tolerate length zero in some operations to allow
 * bootstrapping mechanics that are currently not needed.)
 * 数组表
 */
transient HashMap.Node<K,V>[] table;

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

/**
 * The number of key-value mappings contained in this map.
 * map当前的大小
 */
transient int size;

/**
 * The number of times this HashMap has been structurally modified
 * Structural modifications are those that change the number of mappings in
 * the HashMap or otherwise modify its internal structure (e.g.,
 * rehash).  This field is used to make iterators on Collection-views of
 * the HashMap fail-fast.  (See ConcurrentModificationException).
 * modCount用于记录HashMap的修改次数,
 * 在HashMap的put(),get(),remove(),Interator()等方法中,都使用了该属性
 * 由于HashMap不是线程安全的,所以在迭代的时候,会将modCount赋值到迭代器的expectedModCount属性中,然后进行迭代,
 * 如果在迭代的过程中HashMap被其他线程修改了,modCount的数值就会发生变化,
 * 这个时候expectedModCount和ModCount不相等,
 * 迭代器就会抛出ConcurrentModificationException()异常
 */
transient int modCount;

/**
 * The next size value at which to resize (capacity * load factor).
 * 下一个要调整大小的阈值, 这个值会等于 容量 x 负载因子
 * @serial
 */
int threshold;

/**
 * The load factor for the hash table.
 * 扩容所用的负载因子
 * @serial
 */
final float loadFactor;

 我们使用hashmap都是:

Map<String, Object> mymap = new HashMap<String, Object>();

 找到它的构造函数:

/**
 * Constructs an empty <tt>HashMap</tt> with the default initial capacity
 * (16) and the default load factor (0.75).
 * 默认无参构造函数把默认的负载因子给到了 mymap
 */
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

 这样一个HashMap实例mymap就生成了, 我们给mymap中加点东西.

mymap.put("hello", "world");

去到put方法中瞧一瞧, 直接从外部点击进去会到Map接口中的方法, 直接找到在HashMap中的实现,

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

 先是hash()方法生成key的hash值:

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

再是调用了putVal()方法, 并且最后两个参数的值传了一个false, 一个true, 进去看看:

/**
 * Implements Map.put and related methods.
 *
 * @param hash hash for key 传进来的key的哈希值
 * @param key the key 传进来的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 返回原来key的value值
 */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    /**
     * 此处我传进来的值
     * key : hello
     * value : world
     * onlyIfAbsent : false
     * evict : true
     */
    /**
     * 定义局部变量
     * tab : 局部数组表
     * param : 找到对应i下标的Node
     * tabLength : 局部数组表的长度
     * i : tab的下标
     */
    HashMap.Node<K,V>[] tab;
    HashMap.Node<K,V> param;
    int tabLength, i;
    /**
     * 此时table是一个空数组
     * tab 为null, 执行 resize() 方法(*此方法后续分析)
     */
    if ((tab = table) == null || (tabLength = tab.length) == 0){
        /**
         * 把重新分配的表赋值给tab
         */
        tabLength = (tab = resize()).length;
    }
    /**
     * tab长度-1后与key的hash值得到一个下标赋值给i
     * 获取tab中该下标的值, 如果为null,
     * 则执行newNode()方法生成一个Node节点并且将节点赋值给tab[i]
     */
    if ((param = tab[i = (tabLength - 1) & hash]) == null){
        /**
         * 生成一个Node
         */
        tab[i] = newNode(hash, key, value, null);
    }
    /**
     * 接下来的else分支操作是替换旧值
     */
    else {
        /**
         * 临时节点
         */
        HashMap.Node<K,V> okNode;
        /**
         * 临时key
         */
        K tmpK;
        /**
         * 首先对比 找到的节点的hash值是否和传进来的一致;
         * 如果一致, 继续比较
         * 找到的节点的key值是否和传入的key相等==, 相等判断的是引用地址
         * 如果不相等, 判断一下传入的key是否和找到的节点的key equals,
         * equals方法判断的依据默认也是判断引用地址是否相等, 但是很多类像String,
         * 或者自定义的类, 都有可能重写equals() 方法,
         * 这里加上这么一个判断对传入Object类型的key做了一个是否equals校验的宽容性检查,
         * 如果一个自定义对象, 重写了equals方法, 重写了 hashcode方法, 并且只要该对象的两个实例的id相同,
         * 就判定两个实例相等, 这里就起了关键作用, 因为两个对象实例的引用地址一般都不相等, 除非直接赋值引用.
         *
         * 找到节点的hahs值和传入的hash值相等 而且 找到的节点的key和传入的key也相等,则判定该key存在
         */
        if (param.hash == hash &&
                ((tmpK = param.key) == key || (key != null && key.equals(tmpK))))
        {
            /**
             * 将找到的节点赋给临时节点
             */
            okNode = param;
        }
        /**
         * 继续判断找到的节点是否是树节点,也就是红黑树节点的实例
         */
        else if (param instanceof HashMap.TreeNode)
        {
            /**
             * 如果是红黑树的节点, 则调用putTreeVal()方法更新tab, hash, key, value等需要更新的值
             * 并且返回旧的树节点
             */
            okNode = ((HashMap.TreeNode<K,V>)param).putTreeVal(this, tab, hash, key, value);
        }
        else {
            /**
             * 链表节点的判断和赋值操作
             * 链表操作, 不做过多解释,
             * 嘿嘿, 给自己留点思考的空间吧, 留着以后自己看的时候思考一下
             */
            for (int binCount = 0; ; ++binCount) {
                if ((okNode = param.next) == null) {
                    param.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                    {
                        /**
                         * 链表树形化操作
                         */
                        treeifyBin(tab, hash);
                    }
                    break;
                }
                if (okNode.hash == hash &&
                        ((tmpK = okNode.key) == key || (key != null && key.equals(tmpK))))
                    break;
                param = okNode;
            }
        }
        /**
         * 被返回的树节点, 不是树节点就是链表节点
         */
        if (okNode != null) { // existing mapping for key
            /**
             * 旧节点的值
             */
            V oldValue = okNode.value;
            /**
             * 根据 onlyIfAbsent 值来决定返回的value
             * 如果值没有改变返回的值就是传进来的value自己
             */
            if (!onlyIfAbsent || oldValue == null)
            {
                okNode.value = value;
            }
            /**
             * 空方法
             */
            afterNodeAccess(okNode);
            return oldValue;
        }
    }
    /**
     * 操作数自增1
     */
    ++modCount;
    /**
     * 表的容量自增1之后如果大于要扩容的阈值, 则继续重新计算大小
     */
    if (++size > threshold)
    {
        resize();
    }
    /**
     * 一个空方法, 用户可以自行实现
     */
    afterNodeInsertion(evict);
    /**
     * 此时的值是一个新的值, 所以没有旧的值, 返回null
     */
    return null;
}

那几个空方法:

// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(HashMap.Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(HashMap.Node<K,V> p) { }

到这里put操作就差不多了.

再来看看其他3个构造函数吧!

/**
 * Constructs an empty <tt>HashMap</tt> with the specified initial
 * capacity and the default load factor (0.75).
 *
 * @param  initialCapacity the initial capacity.
 * @throws IllegalArgumentException if the initial capacity is negative.
 * 传入一个初始容量值, 使用默认的负载因子
 */
public HashMap(int initialCapacity) {
    /**
     * 调用的下面的这个构造函数
     */
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

/**
 * Constructs an empty <tt>HashMap</tt> with the specified initial
 * capacity and the default load factor (0.75).
 *
 * @param  initialCapacity the initial capacity.
 * @throws IllegalArgumentException if the initial capacity is negative.
 * 传入一个初始容量值, 使用默认的负载因子
 */
public HashMap(int initialCapacity) {
    /**
     * 调用的下面的这个构造函数
     */
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

/**
 * Constructs an empty <tt>HashMap</tt> with the specified initial
 * capacity and load factor.
 *
 * @param  initialCapacity the initial capacity
 * @param  loadFactor      the load factor
 * @throws IllegalArgumentException if the initial capacity is negative
 *         or the load factor is nonpositive
 * 传入自定义的初始容量, 传入自定义的负载因子
 */
public HashMap(int initialCapacity, float loadFactor) {
    /**
     * 初始容量不能小于0
     * 否则抛出异常
     */
    if (initialCapacity < 0)
    {
        throw new IllegalArgumentException("Illegal initial capacity: " +
                initialCapacity);
    }
    /**
     * 初始容量如果大于最大允许容量, 则使用最大允许容量
     */
    if (initialCapacity > MAXIMUM_CAPACITY)
    {
        initialCapacity = MAXIMUM_CAPACITY;
    }
    /**
     * 负载因子只能是大于0的浮点数
     * 非法的负载因子会抛出异常
     */
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
    {
        throw new IllegalArgumentException("Illegal load factor: " +
                loadFactor);
    }
    this.loadFactor = loadFactor;
    /**
     * 调用方法计算初始容量的 2 的 幂的值
     * 容量大小只允许为2的倍数
     */
    this.threshold = tableSizeFor(initialCapacity);
}

/**
 * Returns a power of two size for the given target capacity.
 * 对给定的容量, 计算出接近该值2倍大小幂的值
 */
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;
}

/**
 * Constructs a new <tt>HashMap</tt> with the same mappings as the
 * specified <tt>Map</tt>.  The <tt>HashMap</tt> is created with
 * default load factor (0.75) and an initial capacity sufficient to
 * hold the mappings in the specified <tt>Map</tt>.
 *
 * @param   m the map whose mappings are to be placed in this map
 * @throws  NullPointerException if the specified map is null
 * 初始化时直接传入一个map, 使用默认的负载因子
 */
public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    /**
     * 将传入的map的条目放进新的map中
     */
    putMapEntries(m, false);
}

/**
 * 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) {
    /**
     * 传入map 的大小
     */
    int size = m.size();
    if (size > 0) {
        /**
         * 先看看自身table是不是null
         */
        if (table == null) { // pre-size
            float ft = ((float)size / loadFactor) + 1.0F;
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                    (int)ft : MAXIMUM_CAPACITY);
            if (t > threshold)
                threshold = tableSizeFor(t);
        }
        /**
         * 看看大小是否超过了下一个要调整的大小, 超过了则重新计算
         */
        else if (size > threshold)
        {
            resize();
        }
        /**
         * 使用entrySet遍历并且put
         * entrySet 这里值得追溯一下 , 仔细点可以看到其实没有一个地方显式的设置entrySet的值,
         * 在put操作里面没有找到相关给entrySet设置值的代码, 这里面的值是怎么来的值得追寻
         */
        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);
        }
    }
}

上面说到entrySet()的值是怎么来的, 其实是从抽象类 AbstractCollection<E> 中的 toString()方法来的;
怎么说呢, 在需要使用entrySet()的时候就会使用到这个方法:

public String toString() {
    Iterator<E> it = iterator();
    if (! it.hasNext())
        return "[]";

    StringBuilder sb = new StringBuilder();
    sb.append('[');
    for (;;) {
        E e = it.next();
        sb.append(e == this ? "(this Collection)" : e);
        if (! it.hasNext())
            return sb.append(']').toString();
        sb.append(',').append(' ');
    }
}

这个方法又会调用 iterator() 方法:

final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
    public final int size()                 { return size; }
    public final void clear()               { HashMap.this.clear(); }
    public final Iterator<Map.Entry<K,V>> iterator() {
        return new EntryIterator();
    }
    public final boolean contains(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>) o;
        Object key = e.getKey();
        Node<K,V> candidate = getNode(hash(key), key);
        return candidate != null && candidate.equals(e);
    }
    public final boolean remove(Object o) {
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>) o;
            Object key = e.getKey();
            Object value = e.getValue();
            return removeNode(hash(key), key, value, true, true) != null;
        }
        return false;
    }
    public final Spliterator<Map.Entry<K,V>> spliterator() {
        return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
    }
    public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
        Node<K,V>[] tab;
        if (action == null)
            throw new NullPointerException();
        if (size > 0 && (tab = table) != null) {
            int mc = modCount;
            for (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next)
                    action.accept(e);
            }
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }
}

Iterator()方法又会new一个EntryIterator , 所以其实是懒加载返回了下一个节点:

final class EntryIterator extends HashIterator
    implements Iterator<Map.Entry<K,V>> {
    public final Map.Entry<K,V> next() { return nextNode(); }
}

这个藏得有点深!

 今天暂时先到这里!

未完待续...

原文地址:https://www.cnblogs.com/fanerwei222/p/11918123.html