浅谈HashMap的默认值、属性和构造器

定义

HashMap基于哈希表的Map实现,以key-value的形式存在,并允许使用 null 值和 null 键。继承AbstractMap,当中实现了Map的重要方法。类定义如下:
public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
复制代码

HashMap综合了ArrayList和LinkList的优点

  1. 使用数组存储数据,索引实现O(1) 时间复杂度,所有查找速度比较快。同ArrayList;
  2. 当发生碰撞时,采用链表存储数据,解决了因hash冲突;同LinkList;
  3. 当链表过长时,导致链表节点的查找性能下降。因此当链表长度超过 8 时, 将链表转变成红黑树,以将O(n) 时间复杂度提升至O(log n)。

默认值

    /**
     * 默认初始容量16,必须是2的幂
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

    /**
     * 最大容量
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 默认负载因子。当键值对的数量大于 CAPACITY * 0.75 时,就会触发扩容
     * 如无参的HashMap构造器,初始化容量为16,当键值对的数量大于 16 * 0.75 = 12时,就会触发扩容
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * 计数阈值。链表的元素大于8的时候,链表将转化为树
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * 计数阈值。resize操作时,红黑树的节点数量小于6时使用链表来代替树
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * 在转变成树之前,还会有一次判断,只有键值对数量大于 64 才会发生转换。
     * 这是为了避免在哈希表建立初期,多个键值对恰好被放入了同一个链表中而导致不必要的转化
     */
    static final int MIN_TREEIFY_CAPACITY = 64;
复制代码

属性

    /**
     * 存储数据的哈希表,默认容量16。长度总是2的幂
     */
    transient Node<K,V>[] table;

    /**
     * Entry集合,主要用于迭代功能
     */
    transient Set<Map.Entry<K,V>> entrySet;

    /**
     * 实际存在的Node数量,不一定等于table的长度(初始化容量),甚至可能大于它。
     */
    transient int size;

    /**
     * HashMap结构被修改的次数,迭代过程中遵循Fail-Fast机制。
     * 保证多线程同时修改map时,能及时的发现(操作前备份的count和当前modCount不相等)并抛出异常终止操作。
     */
    transient int modCount;

    /**
     * 扩容阈值,超过(capacity * loadFactor)时,自动扩容容量为原来的二倍。 
     */
    int threshold;

    /**
     * 负载因子,可计算threshold:threshold = capacity * loadFactor
     */
    final float loadFactor;
复制代码

构造器

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

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

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

        public HashMap(Map<? extends K, ? extends V> m) {
            this.loadFactor = DEFAULT_LOAD_FACTOR;
            putMapEntries(m, false);
        }
复制代码

主要谈谈HashMap(int initialCapacity, float loadFactor);实例化时,内部调用tableSizeFor(initialCapacity)初始化容量;方法如下

    /**
     * 根据整数cap求解大于等于它,且为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;
    }
复制代码

对于求解大于等于它,且为2的整数次幂的最小值,int n = cap - 1;很关键。 假设不执行int n = cap - 1;cap = 8。按照java8的设计思想,因为8>=2^3,结果就应该是8,但结果却是16.

8的二进制:1000
n |= n >>> 1;   n = n | n >>> 1; 1000 | 0100 = 1100
n |= n >>> 2;   n = n | n >>> 2; 1100 | 0011 = 1111
n |= n >>> 4;   n = n | n >>> 4; 1111 | 0000 = 1111
...
n |= n >>> 16; n = 1111 = 15(十进制),最后执行n + 1 = 16
复制代码
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值