源码:常用集合类 之 HashMap

本文详细剖析了HashMap的内部结构,包括其数组加链表或红黑树的实现方式,阐述了属性声明、构造方法、添加、扩容、获取、删除等核心操作的流程与原理。

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


0 前言

  Hashmap 内部结构是数组加链表或数组加红黑树实现。table表示数组,存放map中每个 K-V 都封装成node结构放入链表或者树中。table的每个下标位置就是一个桶,桶内是链表或红黑树。size则表示当前map中node个数。
在这里插入图片描述
  Node 结构:
在这里插入图片描述

1 属性的声明

在这里插入图片描述DEFAULT_INITIAL_CAPACITY:默认初始容量16
MAXIMUM_CAPACITY:最大容量2^30
DEFAULT_LOAD_FACTOR:默认负载因子0.75
TREEIFY_THRESHOLD:树化阈值8,大于等于尝试从链表变为红黑树
UNTREEIFY_THRESHOLD:小于等于尝试从红黑树变为链表
MIN_TREEIFY_CAPACITY:最小树化容量64,map中node个数超过此值才会真正变为红黑树
在这里插入图片描述
table:桶个数
size:map中node节点个数,就是map中元素个数
threshold:阈值默认为默认容量×默认负载因子,16×0.75
loadFactor:负载因子
modeCount:实现fail-fast机制

2 构造方法

在这里插入图片描述

  • 无参构造方法,只是指定负载因子为默认值。在添加元素时候才舒适化table容量
  • 指定容量和负载因子的构造方法,并不会按照我们给的容量初始化table大小(这点和ArrayLisst不同)。
    tableSizeFor(int cap)方法可以看出,容量总是2的n次方,n的取值为2^n-1 < cap <= 2^n。
    在这里插入图片描述
3 添加 put

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 1 计算key的hash值,没有直接使用hashcode而是hash方法主要增加了hash值得随机性。
  • 2 如果table为空则将容量和负载因子先初始化到默认值,调用resize方法。
  • 3 根据hash值计算应该放在哪个桶中,如果桶中没有元素(每个元素是一个node结构),则直接放入,如有有元素则执行步骤4
  • 4.1 每个桶中的元素最初以链表方式存储,挨个遍历链表中元素,寻找hash值与key都相同的node,然后将node的值进行更新,没有找到则直接放入链表尾部,当链表长度达到8会尝试树化。
  • 4.2 如果元素已经是按红黑树的结构存储,则在树中找节点或者加入树。
  • 5 不论是加入链表还是加入树结构中,表示map元素数量size加一,同时说明map结构发生修改,modCount值加一。
  • 6 当size超出扩容阈值threshold(容量*负载因子),调用resize方法扩容。

注意: 1.通过key的hash值与table.length-1做&位运算计算应该放入那个下标的桶中(所以容量为2^n)。2.如果添加的元素的链表达到8会尝试树化(table.length<64只会扩容),下图,链表变为红黑树。3.当map中元素个数达到扩容阈值时,就会调用resize方法扩容(添加完再扩容)。
在这里插入图片描述

4 扩容 resize

在这里插入图片描述在这里插入图片描述在这里插入图片描述

  • 1 计算原容量和原扩容阈值
  • 2.1 如果原容量大于0并且超过默认容量最大值2^30 ,则将原扩容阈值设置为maxint并返回;如果没有超过 2^30,则新容量为原来2倍,新容量不超过最大值,将阈值也扩大为原来2倍。
  • 2.2 如果原容量小于等于0,但是原阈值大于0,将原阈值设置为新容量。
  • 2.3 如果原容量和原扩容阈值都为0,则设置为默认值16和16*0.75,无参构造方法第一次put时走此分支。
  • 3 map中table和扩容阈值都设置为新值,不是初次初始化容量,则需要对原来链表或者树进行拆分,重新散列,根据hash值重新分配到新table的桶中。
  • 4 遍历原table每个桶中元素。如果桶只有一个node则直接散列到新数组中;如果桶中是树,则将树拆分为两个子树,放到新数组中;如果是链表则按hash&oldcap=0拆分为低位高位两个链表,低位链表放在与原table相同下标index的新table的桶中,高位链表放在新table下标index+oldcap的桶中。

注意: 可以发现扩容后对于元素的散列并不是将所有元素取出重新hash&newCap-1计算。考虑最常见情况就是每次容量扩大两倍。下标oldindex的桶中只有一个元素时候,hash&newCap-1计算出来值只可能为oldindex或者是index+oldsize,比如oldindex为6,那么newindex为6或22。oldindex桶中是一个链表的化,拆分的低位链还放在oldindex,高位链在oldindex+oldsize,这样保证了原来不在一个桶中的元素现在也不会在同一个桶中。另外在拆分高低位链表时,根据hash&oldcap=0来判断,从概率上说是50%(只关心hash值右起第五位是否为1)。
在这里插入图片描述
假设key的hash值为54,如上图,所在node节点会放在下标为6的桶中,扩容后重新计算的下标为22。如果在node链表中,则hash&oldcap计算不为0,会放在高位链表,直接放在oldindex+oldsize=6+16=22下标的桶中,所以不需要重新计算hash值。

5 获取 get

在这里插入图片描述
在这里插入图片描述

  • 1 计算key的hash值
  • 2 如果table为空或者根据hash计算出桶为空直接返回空,如果不为空,从树结构或者是链表中寻找相同hash值和key值的node并返回,如果没找到返回null
  • 3 如果node不为null返回node中的值,否则返回null

注意: 返回null并不能说明node不存在,也可能是node的值为null,所以避免歧义通常value为null的元素不应该put。

6 删除 remove

在这里插入图片描述
在这里插入图片描述

  • 1 计算key的hash值
  • 2 如果table为空或者根据hash计算出桶为空直接返回空,如果不为空,从树结构或者是链表中寻找相同hash值和key值的node并返回,如果没找到返回null
  • 3 如果node不为null,则将node删除
  • 4 将修改次数modCount加一,size减一。
7 位运算

  &  与:二进制数假设1为真,0为假。a&b就是二进制每一位&,都为真时结果为真。
  |   或:有一位为真就为真。
  ^  异或:二进制每一位,相同为0,不同为1。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值