HashMap

本文详细探讨了HashMap的重要成员,包括哈希表、节点、大小、负载因子和扩容阈值。讲解了JDK 8中优化的Hash算法,如扰动函数降低冲突,并分析了构造器的行为。进一步阐述了put()方法的寻址操作,以及为何使用按位与而非取模。还讨论了HashMap的扩容、rehash过程、树化条件以及fail-fast机制。特别指出,JDK 1.7中因头插法导致的死循环问题在JDK 1.8中得到了解决。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

重要的成员

1、哈希表:

transient Node<K,V>[] table;

在这里插入图片描述

2、节点

static class Node<K,V> implements Map.Entry<K,V> {
   
	final int hash;
	final K key;
	V value;
	Node<K,V> next;
	...
}

3、哈希表的初始大小,默认16

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

4、哈希表的size

transient int size;

5、负载因子,以及默认值

final float loadFactor;
static final float DEFAULT_LOAD_FACTOR = 0.75f;

一般使用默认值即可

6、扩容阈值

int threshold;

等于数组容量*负载因子


JDK 8 优化的 Hash 算法

JDK 1.7 计算 key 的哈希值时,直接调用 hashCode() 方法。

JDK1.8hash() 方法也叫 “扰动函数”

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

int 类型有 32 位,相当于将 hashCode() 方法的返回值与它的高十六位进行一次异或操作。
优点:降低 Hash 冲突的概率。

面试记得提一下:

因为在后面进行哈希寻址的时候,JDK 1.7 直接使用取模来得到数组下标,JDK 1.8 为了提高效率,变成与 数组长度-1 的值进行与操作得到。而大部分情况下,数组长度都不会超过 2^16,所以需要在计算哈希值时进行一次高低十六位的异或操作,使得计算后的低十六位也包含高十六位的信息,从而避免二进制位的浪费。
即,混合原始哈希码的高位和低位,以此来加大低位的随机性


构造器

默认:没有对 table 数组进行初始化,而是在添加元素时才创建

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

带参数的构造器:可以指定数组的初始化容量和负载因子

public HashMap(int initialCapacity, float loadFactor) {
   
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    // 如果指定的初始化容量大于容量的上限,就直接设置为上限 1<<30
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    // 注意:数组未被初始化时,初始化容量使用字段 threshold 记录
    // 但不是任意一个正整数的值都可以作为数组的容量
    // HashMap 规定数组的容量为 2^n,
    this.threshold = tableSizeFor(initialCapacity);
}

对传入的值做一系列的按位或操作:返回一个刚好大于等于 cap 且为 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;
}

算法的大致流程:
1、抓出 n 最左边的 1
2、让这个 1 右边的数字全部变成 1

最终就得到一个 11…111 的数字。如 n = 0001xxxx
第一次:

n>>>1 = 00001xxx
n = 00001xxx | 0001xxxx = 00011xxx

第二次:

n>>>2 = 0000011x
n = 0000011x | 00011xxx = 0001111x

第三次:

n>>>4 = 00000001
n = 00000001 | 0001111x = 00011111

最终返回 n+1 = 100000


put() 方法的寻址操作

核心方法:HashMap#putVal()

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 为空或者长度为 0,创建
    if ((tab = table) == null || (n = tab.length) == 0)
        // resize() 方法可以用于进行初始化或者扩容
        n = (tab = resize()).length;
        
    // 寻址:计算到数组的下标 i = (n - 1) & hash
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值