虐哭java面试官--聊一聊hashmap
Java hashmap的数据结构,开发的时候从来用不到那么深,MD,每个面试官都要问一遍。
别人恶心我的时候,我要比他更恶心才行。
放心,技术一般的面试官不可能看到我这个深度的。跟他聊聊 loadFactor,聊聊二进制 &运算,聊聊hashmap的resize(),就算不虐哭,也让他倒吸一口凉气。
接下来,技术一般的面试官就不敢问太深的问题了,因为他也不懂啊。
绝对原创,但是都是看的别人的帖子,结合 JDK1.7的源码,断点走出来的结果。
1. 设计结构
结合了数组结构(查询快)和链表结构(插入和删除快1)的特点。
第一层是数组 Entry(K,V) 的一个数组 table,根据key的hashcode值,对当前数组长度-1进行 &运算,得出该键值对 在数组中的存储位置。
然后再判断数组的该位置是否有值,如果该数组位置没有值(null),那么这个键值对的位置就是入住。如果该数组位置有值,那么老主人就作为新主人的一部分,新进来的键值对占据该位置, Entry current.next= oldEntry. 也就形成了链表的结构,上线找下线,下线下面可能还有下线也有可能没有
2. 数组的长度
数组的长度规则:
初始化的时候是16( 2的4次方),每次扩容都是 2的N次方
void addEntry(int hash, K key, V value, intbucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 *table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
为什么取2的N次方,因为在键值对插入的时候,会对index求hashcode值,然后将hashcode值和数组长度-1 进行 &运算
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash =hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
static intindexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "lengthmust be a non-zero power of 2";
return h & (length-1);
}
什么是 &运算?
就是把前后2个值转换为 2进制,相同位置上都为1 则为1,其他的都为0
例如左边图是 16长度数组,也就是 9&15 8&15 运算结果,一个1001(9 十进制) table[9]的位置, 一个1000(8 十进制)table[8]的位置
如果不是2的N次方,那么总数-1 转换为2进制的时候,肯定有一个位置上为0
例如如果数组长度为15,那么 N-1=14, 14转换为二进制就是 1110,
进行&运算的时候,因为1110与任何数进行&运算的结果都不可能是 ***1,所以例如 0001 也就是 table[1]这个位置上始终都不可能有键值对可以插入进去
所以数组的长度只能是2的N次方。
数组长度和实际键值对数量关系
默认初始长度是16,扩容时机的判断2个,所以理论上长度 length=0.75size 到 size 之间。当loadFactor为0.75的时候。
因为当size达到 length的时候,一定会触发 size>=0.75*length 和table[index]!=null。也就是当size超过0.75*length的时候就有概率触发扩容,而且这种触发是随机的。
所以合理的 hashmap的长度应该是 4*size / 3 也就是 1.33*size这样就不可能触发 hashmap的重构。特别是数量比较多的时候,几万个键值对的时候,初始化 hashmap的时候设置长度,非常有意义。
loadFactor的设置
当然也可以根据实际的需求设置 loadFactor,来设置适合业务规则的 hashmap。
如果内存富余,那么建议把loadFactor设置的小一点,但是要注意初始size的设置,如果不合适会导致频繁的 resize 严重影响插入的效率。
如果内存比较吃紧,就可以把loadFactor设置的大一些,但是loadFactor设置大的话,键值对以链表的形式存储的概率就提高,平均的查询时间变慢,但是对于插入而言,虽然没有直接的影响,但是loadFactor提高,
需要插入更多的数据才会触发 resize,这样某种程度上是提升了插入的效率
插入到某个有值的位置,挨个对比是否有 key值相同的对象
2.1)如果有key值一样(hashcode值相同,而且key==oldKey||key.equals(k)),则替换并返回老的key的value值
2.2)如果没有key值一样的,那么该位置会被最后一个进来的Entry 占据,并且Entry的next属性,指向之前第一个位置的Entry,也就是链表了,一个找一个
void addEntry(int hash, K key, V value, intbucketIndex) {
if ((size >=threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
初始化hashmap 2种构造函数,
public HashMap(int initialCapacity, float loadFactor) {
public HashMap(int initialCapacity) {
/**
* Constructs an empty <tt>HashMap</tt> with the specifiedinitial
* capacity and load factor.
*
* @param initialCapacity theinitial capacity
* @param loadFactor the load factor
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor isnonpositive
*/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity:" +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
threshold = initialCapacity;
init();
}
/**
* Constructs an empty <tt>HashMap</tt> with the specifiedinitial
* capacity and the default load factor (0.75).
*
* @param initialCapacity the initialcapacity.
* @throws IllegalArgumentException if the initial capacity is negative.
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
初始化hashmap length的设置
如果不修改 loadFactor的话(默认0.75),那么初始化的length应该为 1.34*size,比如1万个键值对,那么初始化的时候长度给13400比较合适,不会导致resize(),当然也要结合实际的内存情况做权衡
3. 数据的put
3.1)key=null
会直接放在数组的table[0]位置
3.2)插入的位置没有值
直接返回null
3.3)插入的位置已经有值了
3.31)遍历该key是否存在
如果该key已经有值了,那么则替换并返回老的值,
如果该key没有,则该key占据第一个位置,老的 键值对作为链表,存在于 currentEntry.next= oldEntry
判断该key是否已经存在有2个条件 &&,hashcode值相同并且 equals为true,一般实际开发不太可能出现这种情况,除非自己故意设置成这样。
4. hashmap的重构 resize()
会遍历之前所有的 Entry
计算新的 table.index
--- 如果该Entry的 next不为空,则next占据原位置,并且下一个处理这个next
---- 如果待插入的位置已经有Entry,则按照之前的规则,把老的Entry作为 next存在新的 Entry里面。
voidresize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = newEntry[newCapacity];
transfer(newTable,initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity *loadFactor, MAXIMUM_CAPACITY + 1);
}
voidtransfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0: hash(e.key);
}
int i = indexFor(e.hash,newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
虐哭java面试官--聊一聊hashmap相关推荐
- 面试官:HashMap 为什么线程不安全?
面试官:HashMap 为什么线程不安全? 前言:我们都知道HashMap是线程不安全的,在多线程环境中不建议使用,但是其线程不安全主要体现在什么地方呢,本文将对该问题进行解密. 1.jdk1.7中的 ...
- 从Java面试官的角度,如何快速判断程序员的能力
临近年关,今年面试跳槽的人特别多,关注我的朋友都知道我不轻易做分享,因为这没有标准答案,看法也因人而异.但我发现有些面试问题还挺普遍的,今天就说说我做面试官这几年的经验,从面试官的角度去看面试,希望对 ...
- 大企业中,Java面试官最爱问的问题集锦(2)
Java编程语言是一种简单.面向对象.分布式.解释型.健壮安全.与系统无关.可移植.高性能.多线程和动态的语言.如今Java已经广泛应用于各个领域的编程开发. java 面试官:volatile的两点 ...
- 大企业中,Java面试官最爱问的问题集锦
Java编程语言是一种简单.面向对象.分布式.解释型.健壮安全.与系统无关.可移植.高性能.多线程和动态的语言.如今Java已经广泛应用于各个领域的编程开发. Java 面试官:说的还可以,那你知道v ...
- 如果你是一个Java面试官,你会问哪些问题?
作为一名年近40的大龄IT从业人员,在上市公司当经理兼创业公司当总监,从面试上来说也算是阅人无数了吧,所以谈谈个人作为Java面试官,我一般会问的一些问题,希望对你有所收获. 一.请自我介绍 我一般面 ...
- 假如我是JAVA面试官,我会这样虐你
又是金三银四的时候,我希望这份面试题能够祝你一臂之力! 自我和项目相关 1.自我介绍 2.你觉得自己的优点是?你觉得自己有啥缺点? 3.你有哪些 offer? 4.你为什么要离开上家公司?你上家公司在 ...
- 【Java自顶向下】面试官:HashMap源码看过吗?我:看过!面试官:好极了,那么来扒一扒吧!
HashMap 关于hash表的基础内容,请看文章 [数据结构-查找]3.散列表详解 [Java自顶向下]HashMap面试题(2021最新版) 顶层应用 public class HashMapTe ...
- 准备了一周就去字节跳动面试,结果一面就被虐哭了------面试官做个人吧
人们都说,这个世界上有两种人注定单身,一种是太优秀的,另一种是太平凡的. 我一听呀?那我这岂不是就不优秀了吗,于是毅然决然和女朋友分了手. 人们都说,互联网寒冬来了,这个时候还在大面积招人的公司,必然 ...
- 跟Java面试官对线的一天!唬住就要50K,唬不住就要5K
个人面经 前言 JVM篇 计网篇 Java基础篇 多线程篇 Spring框架篇 MyBatis框架篇 MySQL篇 Redis篇 分布式.微服务篇 小结 前言 不积跬步无以至千里,不积小流无以成江海 ...
最新文章
- 【剑指offer】树的子结构
- 微信小程序开发登录界面mysql_微信小程序 欢迎界面开发的实例详解
- 【Groovy】MOP 元对象协议与元编程 ( 方法委托 | 批量方法委托 )
- 如何让API回调你的VC类成员函数而不是静态函数
- 不常见但是有用的 Chrome 调试技巧
- 微型计算机的电池,具有微型计算机芯片的电池蓄电模块、便携式计算机的制作方法...
- Android实现按钮点击效果(第一次点击变色,第二次恢复)
- dw二级联动下拉菜单插件 宋君墨_Excel下拉菜单不会做?15秒教会你制作一二三级联动下拉菜单,从此做表不求人!...
- 【恋上数据结构】希尔排序
- 数字电路逻辑设计_第三版_微课版_第四章思考题与练习题(附答案)
- Flash遮罩之光芒四射、佛光普照
- [OfficeExcel] 王佩丰老师OfficeExcel2010 1-4讲 Excel基本操作 学习笔记
- 卸载Office密钥或删除序列号
- 如何生成密钥文件Snk
- 使用Spire组件抛出异常The type initializer for 'spr857' threw an exception
- 单招学计算机好学吗,单招没被录取学什么,计算机行业
- JavaScript 销毁对象
- 【有利可图网】不懂ps排版?超详细排版教程送上!
- Java并发编程实践之并发理论基础(一)
- # 计算圆周长和面积
热门文章
- xplorer spill address
- Spring注解驱动开发-扩展原理之005-Spring容器刷新第五步-invokeBeanFactoryPostProcessors(beanFactory)
- 微信公众号 授权死循环 问题解决
- 多关键字 多关键词查询方案
- java使用cef,Johness / java-cef / wiki / 使用V49提供的JavaScript Binding进行前后台交互 — Bitbucket...
- PTA公路村村通c++版——山东科技大学
- Prometheus
- 苏州大学文正学院计算机组成期末,苏州大学文正学院操作系统复习题
- 51单片机学习笔记之新建工程、点亮一盏小灯
- 元宇宙与ChatGPT结合 一场颠覆式场景革命或将到来?