设为首页 加入收藏

TOP

HashMap源码,看我这篇就够了(二)
2023-07-23 13:41:47 】 浏览:64
Tags:HashMap 源码
很高,可以降低负载因子loadFactor 的值;相反,如果内存空间紧张而对时间效率要求不高,可以增加负载因子 loadFactor 的值,这个值可以大于1。

  ④、threshold

  计算公式:capacity * loadFactor。这个值是当前已占用数组长度的最大值。过这个数目就重新resize(扩容),扩容后的 HashMap 容量是之前容量的两倍

7 构造函数

①、默认无参构造函数

/**
     * 默认构造函数,初始化加载因子loadFactor = 0.75
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; 
    }

②、指定初始容量的构造函数

/**
     *
     * @param initialCapacity 指定初始化容量
     * @param loadFactor 加载因子 0.75
     */
    public HashMap(int initialCapacity, float loadFactor) {
        //初始化容量不能小于 0 ,否则抛出异常
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        //如果初始化容量大于2的30次方,则初始化容量都为2的30次方
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //如果加载因子小于0,或者加载因子是一个非数值,抛出异常
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }
    // 返回大于等于initialCapacity的最小的二次幂数值。
    // >>> 操作符表示无符号右移,高位取0。
    // | 按位或运算
    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;
    }

8 确定哈希桶数组索引位置

前面我们讲解哈希表的时候,我们知道是用散列函数来确定索引的位置。散列函数设计的越好,使得元素分布的越均匀。HashMap 是数组+链表+红黑树的组合,我们希望在有限个数组位置时,尽量每个位置的元素只有一个,那么当我们用散列函数求得索引位置的时候,我们能马上知道对应位置的元素是不是我们想要的,而不是要进行链表的遍历或者红黑树的遍历,这会大大优化我们的查询效率。我们看 HashMap 中的哈希算法:

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
    i = (table.length - 1) & hash;//这一步是在后面添加元素putVal()方法中进行位置的确定

主要分为三步:

  ①、取 hashCode 值: key.hashCode()

  ②、高位参与运算:h>>>16

  ③、取模运算:(n-1) & hash

  这里获取 hashCode() 方法的值是变量,但是我们知道,对于任意给定的对象,只要它的 hashCode() 返回值相同,那么程序调用 hash(Object key) 所计算得到的 hash码 值总是相同的。

  为了让数组元素分布均匀,我们首先想到的是把获得的 hash码对数组长度取模运算( hash%length),但是计算机都是二进制进行操作,取模运算相对开销还是很大的,那该如何优化呢?

  HashMap 使用的方法很巧妙,它通过 hash & (table.length -1)来得到该对象的保存位,前面说过 HashMap 底层数组的长度总是2的n次方,这是HashMap在速度上的优化。当 length 总是2的n次方时,hash & (length-1)运算等价于对 length 取模,也就是 hash%length,但是&比%具有更高的效率。比如 n % 32 = n & (32 -1)

  这也解释了为什么要保证数组的长度总是2的n次方。

  再就是在 JDK1.8 中还有个高位参与运算,hashCode() 得到的是一个32位 int 类型的值,通过hashCode()的高16位 异或 低16位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度、功效、质量来考虑的,这么做可以在数组table的length比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销。

  下面举例说明下,n为table的长度:

file

9 添加元素

//hash(key)就是上面讲的hash方法,对其进行了第一步和第二步处理
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    /**
     *
     * @param hash 索引的位置
     * @param key  键
     * @param value  值
     * @param onlyIfAbsent true 表示不要更改现有值
     * @param evict false表示table处于创建模式
     * @return
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
            boolean evict) {
         Node<K,V>[] tab; Node<K,V> p; int n, i;
         //如果table为null或者长度为0,则进行初始化
         //resize()方法本来是用于扩容,由于初始化没有实际分配空间,这里用该方法进行空间分配,后面会详细讲解该方法
         if ((tab = table) == null || (n = tab.length) == 0)
             n = (tab = resize()).length;
         //注意:这里用到了前面讲解获得key的hash码的第三步,取模运算,下面的if-else分别是 tab[i] 为null和不为null
         if ((p = tab[i = (n - 1) & hash]) == null)
             tab[i] = newNode(hash, key, value, null);//tab[i] 为null,直接将新的key-value插入到计算的索引i位置
         else {//tab[i] 不为null,表示该位置已经有值了
             Node<K,V> e; K k;
             if (p.hash ==
首页 上一页 1 2 3 4 5 下一页 尾页 2/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Java线程同步的四种方式详解(建议.. 下一篇Jmix 中 REST API 的两种实现

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目