HashMap源码分析之面试必备
今天我们就面试会问到关于HashMap的问题进行一个汇总,以及对这些问题进行解答。
1、HashMap的数据结构是什么?
2、为啥是线程不安全的?
3、Hash算法是怎样实现的?
4、HashMap是如何处理Hash碰撞的?
5、增加元素的方法是怎么实现的?
6、获取元素的方法时怎么实现的?
以上这些问题在面试中出现的频率往往比较高,在对HashMap不太了解的情况下,往往很难将这些问题答全,笔者就带领对这块不熟悉的小伙伴们一起,一步一步解析以上的问题。
1、HashMap的数据结构是什么?
对于这个问题,笔者建议回答的时候对JAVA版本进行区分,因为不同版本下,HashMap的结构是有些差异的。
回答:在JDK1.8之前HashMap是数组+链表的形式,JDK1.8包括之后是数组+链表+红黑树。本文讲的HashMap是基于JDK1.8。
2、为啥是线程不安全的?
多个线程某个时刻同时操作HashMap并执行put操作,且Hash值相同,这个时候需要解决冲突。很多方法如put() 、addEntry() 、resize() 等都不是同步的。
3、Hash算法是如何实现的?
"^"为异或符号其计算机符号为xor,相同为0,相异为1,如0^0=0 、0^1=1.">>>"为右移动符号,左侧补零。图中h>>>16就是将h的高16位换到h的低16位,而之前的高16位全补零。
这里有童鞋可能就会问了,为什么要进行这个向右移16位且异或的操作?
4、HashMap是如何处理Hash碰撞的?
HashMap采用的是链表法,将hash值相同的元素放在一个链表下。
5、增加元素的方法是怎么实现的?
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict
) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 如果说桶(也就是数组,以下都用"桶"代替)为空,或者桶大小为0 则进行初始化
// 这里要区桶大小 和 桶内元素的大小 桶大小是指桶装东西的能力
// 桶内元素大小 是指桶装了多少东西
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 这里是帮助元素查找元素在桶中的定位 如果定位的位置没有元素 那么
// 直接将元素放入桶的该位置就行
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
// 运行到这说明定位的位置已经有元素了
Node<K,V> e; K k;
// 既然有人霸占元素的位置,那么就要与该元素进行对比,看看自己的Hash值和
// key值是不是和该位置的元素一直,如果都一直就记录下该元素以下为e
// 说明有一个和我插入元素的key一样的元素 后续可能要用新值替换旧值
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 如果只是Hash值相等而key不等,这里就是Hash碰撞啦,要解决hash碰撞
// hashMap采用的是链地址法 就是碰撞的元素连成一个链表 这里由于链表
// 如果太长就会树化成红黑树,以下是判断p也就是桶里放的是不是红黑树
else if (p instanceof TreeNode)
// 是红黑树 我们就把节点放入红黑树 注意:这里也不是一定插入到树中,
// 因为如果我们要插入的元素和红黑树中某个节点的key相同的话,也会考虑
// 新值换旧值的问题
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 跳到这 说明p不是树,而是链表 binCount用来记录链表中元素的个数,那么
// 为啥要记录链表中元素的个数呢?主要判断链表是否需要树化成红黑树
for (int binCount = 0; ; ++binCount) {
// e的后一个节点为空 那么直接挂上我们要插入的元素
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// TREEIFY_THRESHOLD 是树化的阈值且其值为8
// 这里要注意:我们要插入的节点p是还没有加到binCount中的
// 也就是说这里虽然binCount>=7就可以树化,其实真正的树化
// 条件是链表中元素个数大于等于8
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 待插入的key在链表中找到了,记录下来然后退出
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 说明找到了key相同的元素
if (e != null) { // existing mapping for key
V oldValue = e.value;
// 判断是否需要旧值换新值,默认情况下是允许更换的
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// 这个方法点进去就是个空方法,主要是为了给继承HashMap的
// LinkedHashMap服务的
afterNodeAccess(e);
return oldValue;
}
}
// 修改次数+1
++modCount;
// 看下达到扩容的阀值没
if (++size > threshold)
// 扩容 ,在本方法的前面需要初始化的时候也出现过
resize();
// 这个方法同样也是为LinkedHashMap服务的
afterNodeInsertion(evict);
// 没找到元素 就返回空
return null;
}
6、获取元素的方法时怎么实现的?
使用hash值去找桶的位置:
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
// 桶不为空 并且桶的元素大于0 同时定位的位置元素还不为空 那就顺藤摸瓜
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
// 第一个元素是不是我们要找的啊?判断一下,是就返回
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
// 第一个元素不是我们要找的,而且后面还接着元素 判断一下是不是树
if (first instanceof TreeNode)
// 是树 按照树的获取节点方法去获取
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
// 到这说明是链表了 那就按照链表的方式去循环
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
7、最后插播一下HashMap中的属性
// 初始容量1向左移动4位为16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 最大容量1向左移30位
static final int MAXIMUM_CAPACITY = 1 << 30;
// 加载因子 也就是桶大小使用要是超过0.75 那么就要考虑扩容了
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 链表太长 要变树的阀值
static final int TREEIFY_THRESHOLD = 8;
// 树变成链表的阀值
static final int UNTREEIFY_THRESHOLD = 6;
// TREEIFY_THRESHOLD达到8也不一定树化,还要容量达到64
static final int MIN_TREEIFY_CAPACITY = 64;
下一期我们对今天讲的put、get方法内涉及到的重要的方法进行讲解。拜了个拜!
大量面试经验以及学习资料书籍请关注微信公众号:AVAJ
回复"offer"进行获取
365篇大厂java面经 你想要的我这里都有
HashMap源码分析之面试必备的更多相关文章
- Java1.7的HashMap源码分析-面试必备技能
HashMap是现在用的最多的map,HashMap的源码可以说是面试必备技能,今天我们试着分析一下jdk1.7下的源码. 先说结论:数组加链表 一.先看整体的数据结构 首先我们注意到数据是存放在一个 ...
- Java BAT大型公司面试必考技能视频-1.HashMap源码分析与实现
视频通过以下四个方面介绍了HASHMAP的内容 一. 什么是HashMap Hash散列将一个任意的长度通过某种算法(Hash函数算法)转换成一个固定的值. MAP:地图 x,y 存储 总结:通过HA ...
- Java源码解析——集合框架(五)——HashMap源码分析
HashMap源码分析 HashMap的底层实现是面试中问到最多的,其原理也更加复杂,涉及的知识也越多,在项目中的使用也最多.因此清晰分析出其底层源码对于深刻理解其实现有重要的意义,jdk1.8之后其 ...
- 【JAVA集合】HashMap源码分析(转载)
原文出处:http://www.cnblogs.com/chenpi/p/5280304.html 以下内容基于jdk1.7.0_79源码: 什么是HashMap 基于哈希表的一个Map接口实现,存储 ...
- Java中HashMap源码分析
一.HashMap概述 HashMap基于哈希表的Map接口的实现.此实现提供所有可选的映射操作,并允许使用null值和null键.(除了不同步和允许使用null之外,HashMap类与Hashtab ...
- JDK1.8 HashMap源码分析
一.HashMap概述 在JDK1.8之前,HashMap采用数组+链表实现,即使用链表处理冲突,同一hash值的节点都存储在一个链表里.但是当位于一个桶中的元素较多,即hash值相等的元素较多时 ...
- HashMap源码分析和应用实例的介绍
1.HashMap介绍 HashMap 是一个散列表,它存储的内容是键值对(key-value)映射.HashMap 继承于AbstractMap,实现了Map.Cloneable.java.io.S ...
- 【Java】HashMap源码分析——常用方法详解
上一篇介绍了HashMap的基本概念,这一篇着重介绍HasHMap中的一些常用方法:put()get()**resize()** 首先介绍resize()这个方法,在我看来这是HashMap中一个非常 ...
- 【Java】HashMap源码分析——基本概念
在JDK1.8后,对HashMap源码进行了更改,引入了红黑树.在这之前,HashMap实际上就是就是数组+链表的结构,由于HashMap是一张哈希表,其会产生哈希冲突,为了解决哈希冲突,HashMa ...
随机推荐
- Spark 的 python 编程环境
Spark 可以独立安装使用,也可以和 Hadoop 一起安装使用.在安装 Spark 之前,首先确保你的电脑上已经安装了 Java 8 或者更高的版本. Spark 安装 访问Spark 下载页面, ...
- springcloud高可用方案
1. 场景描述 公司在规划后续可能会做中台服务,考虑用微服务的方案,让用springcloud部署个简单的高可用Demo. 2. 解决方案 2.1 方案说明 demo用了5台虚拟机: (1)1台gat ...
- MyBatis从入门到精通:update用法、delete用法
update用法: 1.接口类中添加的方法: int updateById(SysUser sysUser); 2.映射文件中添加的代码: <update id="updateById ...
- vue自定义表单生成器,可根据json参数动态生成表单
介绍 form-create 是一个可以通过 JSON 生成具有动态渲染.数据收集.验证和提交功能的表单生成器.并且支持生成任何 Vue 组件.结合内置17种常用表单组件和自定义组件,再复杂的表单都可 ...
- Git的使用和配置小白必看都是干货,为您解惑
Git安装 首先下载git这个软件,然后打开码云新建仓库 在本地选择一个路径作为本地仓库 点新建仓库然后输入邮箱和密码,然后进行配置 在要作为本地仓库的地方新建一个文件夹,保存后关闭,在文件夹空白处鼠 ...
- 【SpringCloud】Ribbon如何自定义客户端配置和全局配置
起因 事情的起因是这样的,公司内部要实现基于Zuul网关的灰度路由,在上线时进行灰度测试,故需要配置业务微服务向Eureka注册的metadata元数据,和自定义Ribbon的负载规则达到只访问灰度服 ...
- Flask项目常见面试问题
一.你的项目中缓存粒度是如何选择的? 缓存粒度一共分为4种. 1.缓存某个数值:一个键只保存一个值,性价比较低,使用率低,如果存储的话我们使用redis的String 2.缓存数据对象:数据库记录对应 ...
- 关于python3与python2同时存在情况下导入pyqt失败解决记录
最近感觉tkinter功能还是比较不适合新手做出高大上的界面,故开始使用pyqt,通过pip安装好了之后,利用qt设计师设计好界面之后,cmd运行之,报错提示没有找到pyqt5模块,IDE运行能正常加 ...
- 动态规划_Sumsets_POJ-2229
Farmer John commanded his cows to search . Here are the possible sets of numbers that sum to : ) +++ ...
- sort+结构体+简单数学+暴力-例题
A-前m大的数 还记得Gardon给小希布置的那个作业么?(上次比赛的1005)其实小希已经找回了原来的那张数表,现在她想确认一下她的答案是否正确,但是整个的答案是很庞大的表,小希只想让你把答案中最大 ...