HashMap解读

HashMap解读

结构 数组+链表+红黑树
算法 一种hash算法(链地址法)
HashMap是一个插入慢、查询快的数据结构

成员变量

1.加载因子loadFactor默认为0.75f
2.Node数组
	static class Node<K,V> implements Map.Entry<K,V>
	
	Node对象
	Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;//next用来标记Node节点的下一个元素
    }
3.modCount修改次数  size逻辑长度(里面放了多少个元素不是容量大小)

方法

在这里插入图片描述
1.new HashMap() 只是计算赋值了一个loadFactor (加载因子)
2.put()
当放入第一个元素时,会触发resize方法,如果底层数组还是null,系统会初始化一个长度为16的Node数组
移位运算符1 << 4 其实就是相当于16(初始化一个长度为16的Node数组)

i = (n - 1) & hash;//hash是传过来的(每个key会有一个计算出来的hash值),其中n是底层数组的长度,用&运算符计算出i的值
p = tab[i];//用计算出来的i的值作为下标从数组中取出元素
if(p == null){//如果这个元素为null,用key,value构造一个Node对象放入数组下标为i的位置
tab[i] = newNode(hash, key, value, null);
}

在数组中放元素位置是乱序的
其中n是数组的长度,假设数组长度为16,不管这个hash的值是多少,经过(n - 1) & hash计算出来的i 的值一定在n-1之间。
刚好是底层数组的合法下标,用i这个下标值去底层数组里去取值,如果为null,创建一个Node放到数组下标为i的位置。
HashMap使用单向链表位置冲突
p.next = newNode(hash, key, value, null);
上面的i计算出来可能该位置已经存在元素,new一个新的Node对象并把当前Node的next引用指向该对象,也就是说原来该位置上只有一个元素对象,现在转成了单向链表
在这里插入图片描述debug图
在这里插入图片描述
红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) //TREEIFY_THRESHOLD的默认值是8
treeifyBin(tab, hash);//把链表转化为红黑树
在这里插入图片描述

初始容量

设置初始容量
initialCapacity = (存储元素个数 / 0.75 + 1)这就大大的减少了扩容的几率
jdk1.8中的putAll方法就是按照此算法实现

扩容
HashMap有扩容机制,就是当达到扩容条件时会进行扩容。HashMap的扩容条件就是当HashMap中的元素个数(size)超过临界值(threshold)时就会自动扩容。在HashMap中,threshold(阀值) = loadFactor(0.75) * capacity(初始容量(为第一个比n大的2的幂))
HashMap中的扩容机制决定了每次扩容都需要重建hash表,是非常影响性能的

扩容步骤
1.扩容:创建一个新的Entry空数组,长度是原数组的2倍。
2.ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。为什么要重新Hash呢?因为长度扩大以后,Hash的规则也随之改变。
hash公式:index = HashCode(Key) & (Length - 1)

key的值已存在Map会直接替换value的内容

在这里插入图片描述
源码实现
1.计算key的hash值,算出元素在底层数组中的下标位置。通过下标位置定位到底层数组里的元素(也有可能是链表也有可能是树)取到元素,判断放入元素的key是否==或equals当前位置的key,成立则替换value值,返回旧值

2.如果是树,循环树中的节点,判断放入元素的key是否==或equals节点的key,成立则替换树里的value,并返回旧值,不成立就添加到树里

3.否则就顺着元素的链表结构循环节点,判断放入元素的key是否==或equals节点的key,成立则替换链表里value,并返回旧值,找不到就添加到链表的最后。

条件
1、hash值相等。
2、==或equals的结果为true。
则将之前value替换并且返回之前value

考题

1、table 的初始化时机是什么时候,初始化的 table.length 是多少、阀值(threshold)是多少,实际能容下多少元素?
在第一次 put 的时候
16
12

2、什么时候触发扩容,扩容之后的 table.length、阀值各是多少?
超过threshold的时候
table.length = 旧table.length2
threshold = 旧table.length
2

3、table的length为什么是2的n次幂?
为了利用位运算 & 求 key 的下标

4、求索引的时候为什么是:h&(length-1),而不是 h&length,更不是 h%length?
h%length 效率不如位运算快
h&length 会提高碰撞几率,导致 table 的空间得不到更充分的利用、降低 table 的操作效率(碰撞多会导致链表多查询速度降低)

5、 Map map = new HashMap(1000); 当我们存入多少个元素时会触发map的扩容
此时的 table.length = 2^10 = 1024; threshold = 1024 * 0.75 = 768; 所以存入第 769 个元素时进行扩容

6、为什么加载因子的默认值是 0.75,并且不推荐我们修改
如果loadFactor太小,那么map中的table需要不断的扩容,扩容是个耗时的过程
如果loadFactor太大,那么map中table放满了也不不会扩容,导致冲突越来越多,解决冲突而起的链表越来越长,效率越来越低
而 0.75 这是一个折中的值,是一个比较理想的值

单向链表和红黑树相互转换

HashMap在jdk1.8之后引入了红黑树的概念,表示若桶中链表元素超过8时,会自动转化成红黑树;若数组中元素小于等于6时,树结构还原成链表形式。

原因:

红黑树的平均查找长度是log(n),长度为8,查找长度为log(8)=3,链表的平均查找长度为n/2,当长度为8时,平均查找长度为8/2=4,这才有转换成树的必要;链表长度如果是小于等于6,6/2=3,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短。

选择6和8的原因是:

中间有个差值7可以防止链表和树之间频繁的转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。

线程安全

多线程下请使用CocurrentHashMap
1.jdk1.7之前在发生hash碰撞,采用头插法方式,出现环形链表死循环(CPU占用100%居高不下)的情况

2.在jdk1.8中对HashMap进行了优化,在发生hash碰撞,直接插入链表尾部,
因此不会出现环形链表死循环情况,但是在多线程的情况下会发生覆盖数据的情况仍然不安全

HashMapJava中的一种数据结构,提供了键值对的存储和查找功能。在HashMap的底层实现中,使用了数组和链表(或者在Java 1.8中使用了红黑树)来解决哈希冲突的问题。 哈希冲突指的是当不同的键对象计算出的哈希值相同时,它们需要被存储在数组的同一个位置上。为了解决哈希冲突,HashMap中使用了两种方法,分别是开放地址法和链地址法。 开放地址法是指当发生哈希冲突时,继续寻找下一个空槽位来存储键值对。这个方法需要保证数组的长度是2的幂次方,通过hash & (length-1)的位运算来减少哈希冲突的概率[2]。 链地址法是指将发生哈希冲突的键值对存储在同一个位置上的链表或红黑树中。这个方法在Java 1.8中使用,当链表的长度超过一定阈值时,会将链表转换为红黑树,以提高查找效率。 在HashMap中,put方法用于插入键值对。当调用put方法时,首先会计算键对象的哈希值,并与数组的长度取余来确定存储位置。如果该位置已经存在键值对,则根据键对象的equals方法来判断是否是同一个键,如果是,则更新对应的值,否则将新键值对插入到链表或红黑树中。如果发生哈希冲突,就会根据选择的解决冲突的方法,继续寻找下一个空槽位或者在链表或红黑树中插入键值对。如果插入后,数组中存储的键值对的数量超过了负载因子(默认为0.75),就会触发扩容操作。 扩容操作会创建一个更大的数组,并将原数组中的键值对重新计算哈希值后插入到新数组中。扩容操作会在数组大小达到阈值(数组长度乘以负载因子)时触发。 总结起来,HashMap的底层实现是通过数组和链表(或红黑树)来解决哈希冲突的问题。它使用哈希值计算和位运算来确定存储位置,同时使用开放地址法和链地址法来解决哈希冲突。在插入键值对时,需要计算哈希值、确定存储位置,并根据解决冲突的方法进行插入。当数组中的键值对数量超过负载因子时,会触发扩容操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [HashMap 底层源码解读(一行一行读,有基础就能看懂)](https://blog.csdn.net/rain67/article/details/124043769)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值