集合—HashMap

HashMap

简介

HashMap 的基础知识

  • HashMap 是由数组+链表+红黑树实现的,

  • 数组长度固定,一个元素进来,先计算其 hash值,然后 hash值 & 数组长度,得到的值就是该元素存放在数组上的下标位置,然后放入链表,

  • 假如链表长度大于8,就从数组转变成红黑树。假如此时整个数组的链表节点大于64,创建新链表的时候就会默认创建红黑树。红黑树可以加快搜索的效率。

  • HashMap 的主干是一个 Entry数组。Entry 是 HashMap 的基本组成单元,每一个 Entry包含一个 key-value 键值对,

  • HashMap 有 4个构造器,

    • public HashMap(int initialCapacity, float loadFactor)

    • public HashMap(int initialCapacity)

    • public HashMap()

    • public HashMap(Map<? extends K, ? extends V> m)

  • 如果用户初始化的时候没有传入 initialCapacity(初始化容量) 和 loadFactor(负载系数,负载因子) 这两个参数,会使用默认值,initialCapacity 默认为16,loadFactory默认为0.75f。initialCapacity 最大值是 1<<30=2^(31-1)≈10亿多。阈值 threshold 的 最大值为 0x7fffffff,即 int 的最大值。

  • 其中负载因子是对 HashMap 空间和时间效率的一个平衡, 因为如果跟 ArrayList 一样等到放不下再进行扩容的时候,某一个 Hash的Key所对应的Value链表可能已经很长了。

HashMap 的添加步骤

  • 在第一次 put 之前,是没有 table 的,table 会在第一次 put 的时候被初始化。

  • 阈值一开始就是 容量加载因子。如果是无参构造,那么就是初始容量 16,初始阈值 12;如果是有参构造,那么就会调用 tableSizeFor方法得到大于输入值的最小的 2 的幂次值,然后把这个值先赋值给阈值,然后让容量等于阈值,然后阈值等于容量加载因子。并且之后每次扩容,table 和阈值都变为原来的两倍。

  • 树化的条件,第一个先决条件是哈希表容量大于等于 64,第二个是一个桶里面的节点个数大等于 8。反树化条件就是小等于 6(8*加载因子)的时候。

  • 扩容之后,旧 key,要么就是呆在老地方,要么就是原位置值 + 旧容量。位置计算公式是 (n-1)& hash,因为每次扩容都是增加一个 bit位,然后 key 通过 hash函数得到的 hash值是不变的,只是 n-1 在变。

  • 扩容时机: 第一:元素个数达到阈值。

第二:hashmap 准备树化但发现数组太短。

hashmap 解决 hash冲突的方法就是:链地址法。

0.75 则是通过概率分布计算得到的,好像是 泊松分布。主要目的就是为了在 hash冲突和空间利用之中找到平衡点。

不增大容量,就会导致元素堆积,并且 hash冲突较多;

增大容量,就是降低空间利用率,但是减少 hash冲突,并且使的平均元素分布,不让一个桶的元素过多;

  1. 建 HashMap, 初始容量为 16, 阈值 = 初始容量*负载因子 (默认 0.75)= 12;

  2. 调用 put方法,会先计算key 的 hash 值:hash = key.hashCode()。

  3. 调用 tableSizeFor()方法,保证哈希表散列均匀。

  4. 计算 Nodes[index]的索引:先进行 index = (tab.length - 1) & hash。

  5. 如果索引位为 null,直接创建新节点,如果不为 null,再判断所因为上是否有元素

  6. 如果有:则先调用hash()方法判断,再调用 equals()方法进行判断,如果都相同则直接用新的 Value 覆盖旧的;

  7. 如果不同,再判断第一个节点类型是否为树节点(涉及到:链表转换成树的阈值,默认 8),如果是,则按照红黑树的算法进行存储;如果不是,则按照链表存储;

  8. 当存储元素过多时,需要进行扩容:默认的负载因子是 0.75,如果实际元素所占容量占分约变为原来的2 倍(newThr = oldThr << 1);

HashMap 中的常量

  • DEFAULT_INITIAL_CAPACITY:默认初始化容量,可以存储元素(Entry)的数量,值为16;

  • MAXIMUM_CAPACITY:最大容量,允许设置的最大存储数量,值为2^30;

  • DEFAULT_LOAD_FACTOR:默认负载因子,用于控制扩容的阈值,值为0.75;

  • TREEIFY_THRESHOLD:链表结构转为红黑树树的阈值,为8,即当链表结构长度达到8时,进行结构转化;

  • UNTREEIFY_THRESHOLD:树结构转为链表的阈值,为6,即当树的节点数达到6时,转化为链表;

  • MIN_TREEIFY_CAPACITY:最小树形化容量阈值,为64,即当HashMap中的元素总数大于64时,才允许将链表转换成红黑树,否则,若桶内元素太多时,则直接扩容,而不是树形化

对 threshold 的理解

问题1

一个 map,负载因子是 0.75,初始化时设定的大小是 10,会被分配16的空间,那么 map容量是在 100.75 还是 160.75 时扩容?

解答

hashmap 在扩容的时候会对 threshold 进行判断,size 大于等于 threshold 的时候就扩容。

然后这个 threshold 就是 2 的次方产生的结果数乘上加载因子,threshold 取的值,是你设定的大小的下一个 2 的次方数,比如 10 的下一个 2 的次方数是 16,所以会在 16*0.75 的时候扩容。

源码理解

tableSizeFor(int cap)

this.threshold = tableSizeFor(initialCapacity);
​
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的次方的结果数。

因为右移做或运算,那就是 1 都移到右边去,然后 int 最高 32位,所以只需要右移 1,2,4,8,16 就行。

比如输入 3,输出 4。输入 5、6、7,输出 8。以输入 10 为例。

int n = 10-1 = 9;// 1001
n = 1001|0100 = 1101 = 13;//>>>1
n = 1101|0011 = 1111 = 15;//>>>2
n = 1111|0000 = 1111 = 15;//>>>4
n = 1111|0000 = 1111 = 15;//>>>8
n = 1111|0000 = 1111 = 15;//>>>16
return n+1=16;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值