深入理解Java之HashMap —— 01

《精要主义》书中的一些感悟:

几乎没有事物的次要性,再怎么高估都不过分。试着利用精要主义打破用忙碌衡量成功的浅见吧。


无论是在工作or面试中,HashMap都是我们经常打交道的。所以今天我们源码的角度来窥探其奥妙。

1.HashMap的结构


1.1 JDK1.7版本

JDK1.7以前的设计,HashMap采用数组+单链表的实现方式,链表就是用来处理Hash冲突的

学过数据结构的人都知道,链表有个缺点:查找速度慢因为必须从头查询,时间复杂度是O(n)

在这里插入图片描述

1.2 JDK1.8版本

所以在JDK 1.8 对这里做了优化,因为我们知道树型结构可以将查找时间复杂度降为O(lg n)

在这里插入图片描述

在链表节点数量超过一定量(8)时,则将链表转化为红黑树, 以后找时间专门来了解一下红黑树。

1.3 关于HashMap结构的总结

HashMap的结构

JDK 1.7 : 数组 + 单链表 (注意这里强调一下
JDK 1.8 :数组 + 单链表 + 红黑树

1.(单)链表的目的:解决了Key的hash冲突问题;
2. 红黑树的目的:加快查询速度;


2. 设计HashMap

2.1 HashMap中的常量值

  1. 默认容量16;
  2. 最大容量2^30;
  3. 默认负载因子 0.75
  4. 可树化的阀值 8 (链表—> 红黑树);
  5. 去树化的阀值 6 (红黑树–>链表 );
  6. 哈希表考虑树形化的最小数量 64;
     /**
     * 默认的初始化变量(16)-必须是2的幂(次方)
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * 最大容量(必须是2的幂且 <= 2的30次方)
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 默认的装载因子(填充比例):0.75,即实际元素所占所分配的容量的75%时就要扩容啦。
     * 如果该值过大,再增加元素将导致链表过长,查找效率降低;反之,浪费空间
     * 关注内存:适当增大填充比;
     * 关注查找性能: 适当减小填充比;
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * 每个桶的可树化的阀值,即当桶中元素个数超过该值时,自动将链表转化为红黑树结构
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * 每个桶的去树化阀值,即在扩容(resize())时,发现桶中的元素少于该值,则将树形结构还原为链表结构
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * 哈希表中可被树形化的最小数量。
     * 即若哈希表中的元素个数少于该值,则桶(bin)不会被树形化,而是会进行扩容(就算桶中的元素个数超过TREEIFY_THRESHOLD)。
     * 主要是为了避免扩容&可树化的选择冲突
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

HashMap的跟容量相关的都必须是2的N次方。

2.2 HashMap中的变量

2.2.1 关键字-transient

用transient关键字标记的成员变量不参与序列化过程,即不会被序列化

2.2.2 HashMap中的变量

如果我们在设计HashMap时,这些基本我们都是要进行考虑的。(PS: 调整次数modCount 确实不知道有什么用, ~233333)

  /***
     * 我们都知道HashMap底层数据结构是:数组+链表
     * 这个参数就是数组,即我们常说的:桶数组。
     * table 在「第一次使用时进行初始化」并在需要的时候重新调整自身大小。对于 table 的大小必须是2的幂次方。
     */
    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;

    /**
     * Map中的键值对的数量
     */
    transient int size;

    /**
     * HashMap 进行结构性调整的次数。结构性调整指的是增加或者删除键值对等操作,注意对于更新某个键的值不是结构特性调整。
     */
    transient int modCount;

    /**
     * The next size value at which to resize (capacity * load factor).
     * Map的容量的阀值:表的大小 capacity * load factor,达到这个容量时进行扩容操作。
     */
    int threshold;

    /**
     * The load factor for the hash table.
     * 负载因子,默认值为 0.75
     */
    final float loadFactor;

目前为止,我们只看到了HashMap结构中的桶数组: transient Node<K,V>[] table;
看链表其实就是看节点Node的。

2.3 HashMap链表节点

单链表

	/**
     * Basic hash bin node, used for most entries.  (See below for
     * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
     * Node 实现了Map.Entry接口
     */
    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;
        }
    }

2.4 HashMap红黑树节点

注意这里继承的是LinkedHashMap.LinkedHashMapEntry<K,V>

LinkedHashMap.LinkedHashMapEntry<K,V> 是双向链表,本质上还是继承自Node的。

  static final class TreeNode<K,V> extends LinkedHashMap.LinkedHashMapEntry<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);
        }

        /**
         * Returns root of tree containing this node.
         */
        final TreeNode<K,V> root() {
            for (TreeNode<K,V> r = this, p;;) {
                if ((p = r.parent) == null)
                    return r;
                r = p;
            }
        }
       ...
}

这篇文章就到这里,干货貌似不多…

因为这篇文章的目的主要是让大家对于HashMap有个概括上的认识。

欢迎继续阅读下一篇 深入理解Java之HashMap —— 02

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章