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相关推荐

  1. 面试官:HashMap 为什么线程不安全?

    面试官:HashMap 为什么线程不安全? 前言:我们都知道HashMap是线程不安全的,在多线程环境中不建议使用,但是其线程不安全主要体现在什么地方呢,本文将对该问题进行解密. 1.jdk1.7中的 ...

  2. 从Java面试官的角度,如何快速判断程序员的能力

    临近年关,今年面试跳槽的人特别多,关注我的朋友都知道我不轻易做分享,因为这没有标准答案,看法也因人而异.但我发现有些面试问题还挺普遍的,今天就说说我做面试官这几年的经验,从面试官的角度去看面试,希望对 ...

  3. 大企业中,Java面试官最爱问的问题集锦(2)

    Java编程语言是一种简单.面向对象.分布式.解释型.健壮安全.与系统无关.可移植.高性能.多线程和动态的语言.如今Java已经广泛应用于各个领域的编程开发. java 面试官:volatile的两点 ...

  4. 大企业中,Java面试官最爱问的问题集锦

    Java编程语言是一种简单.面向对象.分布式.解释型.健壮安全.与系统无关.可移植.高性能.多线程和动态的语言.如今Java已经广泛应用于各个领域的编程开发. Java 面试官:说的还可以,那你知道v ...

  5. 如果你是一个Java面试官,你会问哪些问题?

    作为一名年近40的大龄IT从业人员,在上市公司当经理兼创业公司当总监,从面试上来说也算是阅人无数了吧,所以谈谈个人作为Java面试官,我一般会问的一些问题,希望对你有所收获. 一.请自我介绍 我一般面 ...

  6. 假如我是JAVA面试官,我会这样虐你

    又是金三银四的时候,我希望这份面试题能够祝你一臂之力! 自我和项目相关 1.自我介绍 2.你觉得自己的优点是?你觉得自己有啥缺点? 3.你有哪些 offer? 4.你为什么要离开上家公司?你上家公司在 ...

  7. 【Java自顶向下】面试官:HashMap源码看过吗?我:看过!面试官:好极了,那么来扒一扒吧!

    HashMap 关于hash表的基础内容,请看文章 [数据结构-查找]3.散列表详解 [Java自顶向下]HashMap面试题(2021最新版) 顶层应用 public class HashMapTe ...

  8. 准备了一周就去字节跳动面试,结果一面就被虐哭了------面试官做个人吧

    人们都说,这个世界上有两种人注定单身,一种是太优秀的,另一种是太平凡的. 我一听呀?那我这岂不是就不优秀了吗,于是毅然决然和女朋友分了手. 人们都说,互联网寒冬来了,这个时候还在大面积招人的公司,必然 ...

  9. 跟Java面试官对线的一天!唬住就要50K,唬不住就要5K

    个人面经 前言 JVM篇 计网篇 Java基础篇 多线程篇 Spring框架篇 MyBatis框架篇 MySQL篇 Redis篇 分布式.微服务篇 小结 前言 不积跬步无以至千里,不积小流无以成江海 ...

最新文章

  1. 【剑指offer】树的子结构
  2. 微信小程序开发登录界面mysql_微信小程序 欢迎界面开发的实例详解
  3. 【Groovy】MOP 元对象协议与元编程 ( 方法委托 | 批量方法委托 )
  4. 如何让API回调你的VC类成员函数而不是静态函数
  5. 不常见但是有用的 Chrome 调试技巧
  6. 微型计算机的电池,具有微型计算机芯片的电池蓄电模块、便携式计算机的制作方法...
  7. Android实现按钮点击效果(第一次点击变色,第二次恢复)
  8. dw二级联动下拉菜单插件 宋君墨_Excel下拉菜单不会做?15秒教会你制作一二三级联动下拉菜单,从此做表不求人!...
  9. 【恋上数据结构】希尔排序
  10. 数字电路逻辑设计_第三版_微课版_第四章思考题与练习题(附答案)
  11. Flash遮罩之光芒四射、佛光普照
  12. [OfficeExcel] 王佩丰老师OfficeExcel2010 1-4讲 Excel基本操作 学习笔记
  13. 卸载Office密钥或删除序列号
  14. 如何生成密钥文件Snk
  15. 使用Spire组件抛出异常The type initializer for 'spr857' threw an exception
  16. 单招学计算机好学吗,单招没被录取学什么,计算机行业
  17. JavaScript 销毁对象
  18. 【有利可图网】不懂ps排版?超详细排版教程送上!
  19. Java并发编程实践之并发理论基础(一)
  20. # 计算圆周长和面积

热门文章

  1. xplorer spill address
  2. Spring注解驱动开发-扩展原理之005-Spring容器刷新第五步-invokeBeanFactoryPostProcessors(beanFactory)
  3. 微信公众号 授权死循环 问题解决
  4. 多关键字 多关键词查询方案
  5. java使用cef,Johness / java-cef / wiki / 使用V49提供的JavaScript Binding进行前后台交互 — Bitbucket...
  6. PTA公路村村通c++版——山东科技大学
  7. Prometheus
  8. 苏州大学文正学院计算机组成期末,苏州大学文正学院操作系统复习题
  9. 51单片机学习笔记之新建工程、点亮一盏小灯
  10. 元宇宙与ChatGPT结合 一场颠覆式场景革命或将到来?