HashMap高级

1.HashMap简介

HashMap集合:是基于哈希表的Map接口实现,是以key-value存储形式存在的(key,value都可以为null),主要用来存放键值对

HashMap的实现不是同步的,意味着它不是线程安全的和映射不是有序的

JDK1.8之前:HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突(两个对象调用的hashCode方法计算的哈希码值一致导致计算的数组索引值相同)而存在的(拉链法解决冲突)

JDK1.8之后:在解决哈希冲突时有了较大的变化,当链表长度大于阀值(或者红黑树的边界值,默认为8)并且当前数组的长度大于64时,此索引位置上的所有数据改为使用红黑树存储

JDK版本实现方式节点数>=8
1.8之前数组+单向链表数组+单向链表
1.8之后数组+单向链表+红黑树数组+红黑树

补充:将链表转换成红黑树前会判断,即使阀值大于8,但是数组长度小于64,并不会将链表变成红黑树,而是选择进行数组扩容

2.HashMap集合的数据结构存储过程

数据结构:存储数据的一种方式

面试题:

1.哈希表底层采用何种算法计算hash值?还有那些算法可以计算出hash值?

底层采用key的hashCode()方法的值结合数组长度进行无符号右移(>>>),按位异或(^),按位与(&)计算出索引

还可以采用:平方取中法,取余法,伪随机数法

2.当两个对象的hashCode值相等时会怎样

会产生哈希碰撞,若key值内容相同则替换旧的value,否则连接到链表后面,链表长度超过阀值8并且数组大于64就转换为红黑树存储

3.何时发生哈希碰撞和什么是哈希碰撞?如何解决哈希碰撞?

只要两个元素的key计算的哈希值相同就会发生哈希碰撞。JDK8前使用链表解决哈希碰撞;JDK8之后使用链表+红黑树解决哈希碰撞

如果两个键的hashcode相同,如何存储键值对?

hashcode相同,通过equals()方法比较内容是否相同

相同:新的value覆盖之前的value

不相同:将新的键值对添加到哈希表中

默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来

3.HashMap集合的成员变量

1.序列化版本号(serialVersionUID)

2.集合的初始化容量(DEFAULT_INITIAL_CAPACITY默认的初始化容量是16,必须是2的n次幂

为什么必须是2的n次幂?如果输入值不是2的幂比如10会怎么样?

(1)当我们根据key的hash确定其在数组的位置时,如果n为2的幂次方,可以保证数据的均匀插入,如果n不是2的幂次方,可能数组的一些位置永远不会插入数据,浪费数组的空间,加大hash冲突

(2)HashMap容量为2次幂是为了数据的均匀分布,减少hash冲突,毕竟hash冲突越大,代表数组种一个链的长度越大,会降低hashmap的性能

(3)如果创建HashMap对象时,输入的数组的长度是10,不是2的幂,HashMap通过位移运算和或运算得到的肯定是2的幂次方,并且是离那个数字最近的幂方(例如10,会得到16)

HashMap如果创建集合时指定的容量不是2的n次幂的情况?

如果数组长度不是2的n次幂,计算出的索引特别容易相同,及其容易发生hash碰撞,导致其余数组空间很大程度上并没有存储数据,链表或者红黑树过长,效率降低

3.默认的负载因子,默认值是0.75(DEFAULT_LOAD_FACTOR)

4.集合最大容量(MAXIMUM_CAPACITY上限是:2的30次幂)

5.当链表的值超过8则会转红黑树(1.8之后新增)

为什么Map桶中节点个数超过8才转为红黑树?

推荐文章:https://www.cnblogs.com/linghu-java/p/10598758.html

6.当链表的值小于6则会从红黑树转回链表(UNTREEIFY_THRESHOLD)

7.桶中结构转化为红黑树对应的数组长度最小的值(MIN_TREEIFU_CAPACITY)

8.table用来初始化(必须是2的n次幂)

负载存储键值对数据的。JDK1.8之前数组类型是Entry<K,V>类型,JDK1.8之后是Node<K,V>类型,都是实现了Map.Entry<K,V>接口

9.HashMap存放元素的个数(size)

存放元素的个数,不等于数组的长度

10.用来记录HashMap的修改次数(modCount)

11.用来调整大小下一个容量的值计算方式(threshold=容量*负载因子)

12.哈希表的加载因子(loadFactor)

加载因子(loadFactor)是用来衡量HashMap的疏密程度,影响hash操作到同一个数组位置的概率

计算HashMap的实时加载因子的方法:size(数组实时存放元素)/capacity(桶的数量)

加载因子太大导致查找元素效率低,太小导致数组的利用率低,存放的数据会很分散,loadFactor的默认值为0.75f是官方给出的一个比较好的临界值

当hashMap里面容纳的元素已经达到HashMap数组长度的75%时,表示HashMap太挤了,需要扩容,而扩容这个过程设计到rehash,复制数据等操作,非常消耗性能。所以开发中尽量减少扩容的次数,可以通过创建HashMap集合对象时指定初始容量来尽量避免

总结:既兼顾数组利用率又考虑链表不要太多,经过大量测试0.75是最佳方案

threshold计算公式:capacity(数组长度默认16)*loadFactor(负载因子默认0.75).这个值是当前已占用数组长度的最大值。衡量数组是否需要扩增的标准:当Size>=threshold的时候,要考虑对数组的resize(扩容),扩容后的HashMap容量是之前容量的两倍

4.HashMap集合的成员方法

put()方法推荐文章:https://blog.csdn.net/meng_lemon/article/details/88906980

treeifyBin()方法:用于链表转化为红黑树的方法(https://www.cnblogs.com/flydoging/p/10384362.html)

扩容机制

1.什么时候才需要扩容?

(1)当HashMap的元素个数超过数组大小(数组长度)*loadFactor(负载因子)时,就会进行扩容

(2)当HashMap中的其中一个链表的对象个数如果达到了8个,此时如果数组长度没有达到64,那么HashMap会先扩容解决,如果已经达到课了64,那么这个链表会变成红黑树,节点类型由Node变成TreeNode类型。当然,如果映射关系被移除后,下次执行resize()方法时判断树的节点个数低于6,也会在把树转换为链表

2.HashMap的扩容是什么

1.8之前:进行扩容会伴随一次重新hash分配,并且会历遍hash表中所有的元素,是非常耗时的(编写程序要尽量避免resize)

1.8之后:进行扩容时,使用的rehash方式非常巧妙,因为每次扩容都是翻倍,与原来计算的(n-1)&hash的结果相比,只是多了一个bit位,所以节点要么在原来的位置,要么就被分配到“原位置+旧容量”的位置

resize()方法:负责扩容的方法

remove()方法:删除元素的方法

get()方法:负责查找元素

5.历遍HashMap集合的四种方式

(1)分别遍历Key和Values

(2)使用Iterator迭代器迭代

(3)通过get方式(效率低不建议使用)

(4)JDK8之后使用Map接口中的默认方法

6.HashMap的初始化操作

如果知道由多少键值对需要存储,在初始化HashMap的时候指定它的容量,以防止HashMap自动扩容,影响使用效率

初始化容量设置成多少效率更高?

关于这个值的设置,在《阿里巴巴Java开发手则》有以下建议:

initialCapacity=(需要存储的元素个数 / 负载因子(默认值为0.75))+1

例如设置值是7,通过上面公式:7/0.75+1=10,10经过JDK处理之后,会被设置成16,这就大大的减少了扩容几率

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值