HashMap攻略要点

HashMap攻略要点

本文以JDK1.8为基础,JDK1.7仅做简略提及。

HashMap的底层结构和原理?

JDK 1.7 中 HashMap 的底层数据结构是数组 + 链表,使用 Entry 类存储 Key 和 Value;

JDK 1.8 中 HashMap 的底层数据结构是数组 + 链表/红黑树,使用 Node 类存储 Key 和 Value。

如何解决哈希冲突?

因为哈希冲突是不可避免的,很有可能两个元素计算得出的 index 是相同的,那么如何解决哈希冲突呢?拉链法。也就是把 hash 后值相同的元素放在同一条链表上。

什么时候HashMap中的链表转为红黑树?

当链表的长度大于 8 的时候就会转换为红黑树,不过,在转换之前,会先去查看 table 数组的长度是否大于 64,如果数组的长度小于 64,那么 HashMap 会优先选择对数组进行扩容 resize,而不是把链表转换成红黑树。

HashMap的简要图示?

JDK1.8下的HashMap结构

在这里插入图片描述

新的Entry/Node节点插入链表的时候,是怎么插入的?

JDK1.7的时候,采用的是头插法。 JDK 1.7 中采用的头插法在多线程环境下可能会造成循环链表问题。

JDK1.8,改成了尾插法。由于 JDK 1.7 中 HashMap 使用头插会改变链表上元素的的顺序,在旧数组向新数组转移元素的过程中修改了链表中节点的引用关系,因此 JDK 1.8 改成了尾插法,在扩容时会保持链表元素原本的顺序,避免了链表成环的问题。

默认初始数组长度是多少?为什么是这么多?

HashMap的扩容机制是什么?

1)扩容:创建一个新的 Entry/Node 空数组,长度是原数组的 2 倍;

2)ReHash:遍历原 Entry/Node 数组,把所有的 Entry/Node 节点重新 Hash 到新数组。

为什么我们需要ReHash?

因为数组的长度改变以后,Hash 得到的结果也随之改变。所以需要ReHash。

Hash 的规则:index = HashCode(key) & (Length - 1)

介绍完HashMap的扩容机制,接下来我们重新进行讲解。

HashMap默认数组长度?

默认数组长度是 16,其实只要是 2 的次幂都行。

为什么是这么多?

常用的 Hash 函数是这样的:index = HashCode(key) % Length,但是因为位运算的效率比较高嘛,所以 HashMap 就相应的改成了这样:index = HashCode(key) & (Length - 1)

为了保证根据上述公式计算出来的 index 值是分布均匀的,我们就必须保证 Length 是 2 的次幂。

2 的次幂,也就是 2 的 n 次方,它的二进制表示就是 1 后面跟着 n 个 0,那么 2 的 n 次方 - 1 的二进制表示就是 n 个 1。如15的二进制后四位为1111,这样与1或0进行与运算时,得到的结果可能为1或0,不单单为1或0。也就是说,index 的结果等同于 HashCode(key) 后 n 位的值,只要 HashCode 本身是分布均匀的,那么我们这个 Hash 算法的结果就是均匀的。

总结,HashMap以2倍扩容,目的就是减少hash碰撞,使元素分配均匀。

为什么重写equals方法的时候还要重写hashCode方法?

==运算符

对于基本数据类型来说, == 比较的是值是否相同;

对于引用数据类型来说, == 比较的是内存地址是否相同。

equals()

情况 1:没有重写 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过 == 比较这两个对象(比较的是地址)。

情况 2:重写 equals() 方法。一般来说,我们都会重写 equals() 方法来判断两个对象的内容是否相等,比如 String 类就是这样做的。

如果我们不重写 hashCode(),那么任何对象的 hashCode() 值都不会相等

HashMap 中get(key) 一个元素的过程是这样的,先比较 key 的 hashcode() 是否相等,若相等再通过 equals() 比较其值,若 equals() 相等则认为他们是相等的。若 equals() 不相等则认为他们不相等。

如果只重写 equals 没有重写 hashCode(),就会导致相同的对象却拥有不同的 hashCode,也就是说在判断的第一步 HashMap 就会认为这两个对象是不相等的,那显然这是错误的。

HashMap线程不安全的表现?

由于 put 操作并没有上锁,并发环境下可能发生某个线程插入的数据被覆盖的问题

假设线程 1 和线程 2 同时进行 put 操作,恰好这两条不同的数据的 hash 值是一样的,并且该位置数据为null,这样,线程 1 和线程 2 都会进入这段代码进行插入元素。假设线程 1 进入后还没有开始进行元素插入就被挂起,而线程 2 正常执行,并且正常插入数据,随后线程 1 得到 CPU 调度进行元素插入,这样,线程 2 插入的数据就被覆盖了。

如何保证HashMap线程安全?

  • 使用synchronizedMap方法包装HashMap,得到线程安全的HashMap
  • 使用线程安全的 HashTable 类代替,该类在对数据操作的时候都会上锁,也就是加上 synchronized
  • 使用线程安全的 ConcurrentHashMap 类代替。JDK 1.8 采用数组 + 链表/红黑树存储数据,使用 CAS + synchronized 保证线程安全。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值