HashMap中常见面试题

本文围绕面试常见的Java中HashMap和ConcurrentHashMap展开。介绍了HashMap不同版本底层数据结构,分析其线程不安全原因,解释长度为2的幂次方的原理及put流程。还阐述了ConcurrentHashMap在JDK1.7和JDK1.8的底层结构及线程安全实现方式。

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

1.HashMap的底层数据结构
  1. JDK1.8 之前
    JDK1.8之前HashMap底层是数组和链表结合在一起使用也就是链表散列,通过拉链法来解决hash冲突。
    所谓 “拉链法” 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。

  2. JDK1.8 之后
    JDK1.8之后对底层结构进行了优化,在原有基础之上添加了红黑树。当发生hash冲突,且链表长度大于8时,会将链表转换为红黑树。

2.HashMap线程安全吗?为什么?

不安全。
JDK1.8之前原因在于并发下的 Rehash 会造成元素之间会形成一个循环链表。(链表头插)
JDK1.8之后修改为链表尾插,不再发生环形,会出现数据的覆盖问题。
所以HashMap线程不安全,推荐使用ConcurrentHashMap

3.HashMap 的长度为什么是 2 的幂次方

HashMap存取时,都需要计算当前key应该对应Entry[]数组哪个元素,即计算数组下标。
计算数组下标的算法其实就是取模:用hash值对map的数组长度进行取模获取数组下标(1.8以前)。计算机中直接求余效率不如位移运算,源码中做了优化hash&(length-1)(1.8以后),
什么时候hash%length和hash&(length-1)相等呢?
答案就是length是2的n次方
为什么这样能均匀分布减少碰撞呢?2的n次方二进制就是1后面n个0,2的n次方-1 实际就是n个1;
例如长度为9时候,3&(9-1)=0 2&(9-1)=0 ,都在0上,碰撞了;
例如长度为8时候,3&(8-1)=3 2&(8-1)=2 ,不同位置上,不碰撞;

4.HashMap 的put流程

1.计算关于key的hashcode值(数组)
2.如果散列表为空时,调用resize()初始化散列表
3.如果没有发生碰撞,直接添加元素到散列表中去
4.如果发生了碰撞(hashCode值相同),进行三种判断

  • 若key地址相同或者equals后内容相同,则替换旧值
  • 如果是红黑树结构,就调用树的插入方法
  • 如果是链表结构,循环遍历直到链表中某个节点为空,尾插法进行插入,插入之后判断链表个数是否到达变成红黑树的阙值8;也可以遍历到有节点与插入元素的哈希值和内容相同,进行覆盖。
    5.如果桶满了大于阀值,则resize进行扩容
5.ConcurrentHashMap 线程安全的具体实现方式/底层具体实现

**底层数据结构:**JDK1.7 的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟 HashMap1.8 的结构一样,数组+链表/红黑二叉树。

**实现线程安全的方式: **在 JDK1.7 的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 到了 JDK1.8 的时候已经摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。

**线程安全实现&示意图: **

  1. JDK1.7:

首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
图中的Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁。
一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和 HashMap 类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个 HashEntry 数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 的锁。

  1. JDK1.8:

ConcurrentHashMap 取消了 Segment 分段锁,采用 CAS 和 synchronized 来保证并发安全。数据结构跟 HashMap1.8 的结构类似,数组+链表/红黑二叉树。
在进行插入时,若当前数组下标是否第一次插入,如果是就通过 CAS 方式插入。synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java码农杂谈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值