作者:孤独烟 出处: http://rjzheng.cnblogs.com/

文章由点及线再及面,写的非常好。修改部分内容

参考资料2:美团技术团队 https://tech.meituan.com/2016/06/24/java-hashmap.html

(1) HashMap的实现原理

此题可以分为下面几个小问题来问

• 看过HashMap源码吗,知道原理吗?

• 为什么用数组+链表?

• hash冲突你还知道哪些解决办法?

• 用LinkedList代替数组结构可以么?

• 了解TreeMap吗?

看过HashMap源码吗,知道原理吗?

HashMap采用Entry数组来存储key-value对,每一个键值对组成了一个Entry实体,Entry类实际上是一个单向的链表结构,它具有Next指针,可以连接下一个Entry实体。 只是在JDK1.8中,链表长度大于8的时候,链表会转成红黑树。

为什么用数组+链表?

数组是用来确定桶的位置,利用元素的key的hash值对数组长度取模得到. 链表是用来解决hash冲突问题,当出现hash值一样的情形,就在数组上的对应位置形成一条链表。

ps: 这里的hash值并不是指hashcode,而是将hashcode高低十六位异或过的。至于为什么要这么做,继续往下看。

hash冲突你还知道哪些解决办法?

比较出名的有四种 (1)开放定址法 (2)链地址法 (3)再哈希法 (4)公共溢出区域法

HashMap中使用的是链地址法。

用LinkedList代替数组结构可以么?

当然是可以的,稍微说明一下,此题的意思是,源码中是这样的

Entry[] table = new Entry[capacity];

ps: Entry就是一个链表节点。 那下面这样表示,是否可行?

List<Entry> table = new LinkedList<Entry>();

答案很明显,是可以的。

既然是可以的,为什么HashMap不用LinkedList,而选用数组?

因为用数组效率最高!在HashMap中,定位桶的位置是利用元素的key的哈希值对数组长度取模得到。此时,我们已得到桶的位置。显然数组的查找效率比LinkedList大。

那ArrayList,底层也是数组,查找也快啊,为啥不用ArrayList?

因为采用基本数组结构,扩容机制可以自己定义,HashMap中数组扩容刚好是2的次幂,在做取模运算的效率高。 而ArrayList的扩容机制是1.5倍扩容,那ArrayList为什么是1.5倍扩容这就不在本文说明了。

了解TreeMap吗?

TreeMap 则是基于红黑树的一种提供顺序访问的 Map,和HashMap不同,它的get、put、remove之类操作都是O(logn)的复杂度,具体顺序可以由指定的Comparator来决定,或者根据键的自然顺序来判断

了解CurrentHashMapp吗

Java8 ConcurrentHashMap结构基本上和Java8的HashMap一样,使用synchronized+CAS来保证线程安全性。

(2) HashMap在什么条件下扩容?

• HashMap在什么条件下扩容?

• 为什么扩容是2的n次幂?

• 为什么要先高16位异或低16位再取模运算?

HashMap在什么条件下扩容?

如果bucket满了(超过load factor*current capacity),就要resize。 load factor为0.75,为了最大程度避免哈希冲突 current capacity为当前数组大小。

为什么扩容是2的次幂?

HashMap为了存取高效,要尽量较少碰撞,就是要尽量把数据分配均匀,每个链表长度大致相同,这个实现就在把数据存到哪个链表中的算法

这个算法实际就是取模,hash%length。 但是,大家都知道这种运算不如位移运算快。

因此,源码中做了优化hash&(length-1)。 也就是说hash%length==hash&(length-1)

为什么要先高16位异或低16位再取模运算?

来看一下jdk1.8里的hash方法源码。1.7的比较复杂,就不看了。

static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

hashmap这么做,是为了降低hash冲突的几率。

(3) 讲讲HashMap的get/put的过程?

• 知道hashmap中put元素的过程是什么样吗?

• 知道hashmap中get元素的过程是什么样吗?

• hash算法是干嘛的?还知道哪些hash算法?

• 说说String中hashcode的实现?(常考)

知道hashmap中put元素的过程是什么样吗?

对key的hashCode()做hash运算,计算index; 如果没碰撞直接放到bucket里; 如果碰撞了,以链表的形式存在buckets后; 如果碰撞导致链表过长(大于等于TREEIFY_THRESHOLD),就把链表转换成红黑树(JDK1.8中的改动); 如果节点已经存在就替换old value(保证key的唯一性) 如果bucket满了(超过load factor*current capacity),就要resize。

知道hashmap中get元素的过程是什么样吗?

对key的hashCode()做hash运算,计算index; 如果在bucket里的第一个节点里直接命中,则直接返回; 如果有冲突,则通过key.equals(k)去查找对应的Entry;

• 若为树,则在树中通过key.equals(k)查找,O(logn);

• 若为链表,则在链表中通过key.equals(k)查找,O(n)。

hash算法是干嘛的?还知道哪些hash算法?

Hash函数是指把一个大范围映射到一个小范围。把大范围映射到一个小范围的目的往往是为了节省空间,使得数据容易保存。

比较出名的算法有MD4、MD5等

说说String中hashcode的实现?

public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value; for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}

String类中的hashCode计算方法还是比较简单的,就是以31为权,每一位为字符的ASCII值进行运算,用自然溢出来等效取模。

哈希计算公式可以计为s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

那为什么以31为质数呢?

主要是因为31是一个奇质数,所以31*i=32*i-i=(i<<5)-i,这种位移与减法结合的计算相比一般的运算快很多。

(4) 为什么HashMap的在链表元素数量超过8时改为红黑树?

• jdk1.8中hashmap与之前有哪些不同?

• 为什么在解决hash冲突的时候,不直接用红黑树?而选择先用链表,再转红黑树?

• 不用红黑树,用二叉查找树可以么?

• 当链表转为红黑树后,什么时候退化为链表?

jdk1.8中HashMap与之前有哪些不同?

• 由数组+链表的结构改为数组+链表+红黑树。

• 优化了高位运算的hash算法:h^(h>>>16)

• 扩容后,元素要么是在原位置,要么是在原位置再移动2次幂的位置,且链表顺序不变。

最后一条是重点,因为最后一条的变动,HashMap在1.8中,不会在出现死循环问题。

为什么在解决hash冲突的时候,不直接用红黑树?而选择先用链表,再转红黑树?

因为红黑树需要进行左旋,右旋,变色这些操作来保持平衡,而单链表不需要。 当元素小于8个当时候,此时做查询操作,链表结构已经能保证查询性能。当元素大于8个的时候,此时需要红黑树来加快查询速度,但是新增节点的效率变慢了。

不用红黑树,用二叉查找树可以么?

二叉查找树在特殊情况下会退化成一条线性结构

当链表转为红黑树后,什么时候退化为链表?

为6的时候退转为链表。中间有个差值7可以防止链表和树之间频繁的转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。

(5) HashMap的并发问题?

• HashMap在并发编程环境下有什么问题(jdk1.8以后)?

• 如何解决这些问题?

HashMap在并发编程环境下有什么问题(jdk1.8以后)?

• 多线程put的时候可能导致元素丢失

• put非null元素后get出来的却是null

如何解决这些问题?

使用ConcurrentHashmap,Hashtable等线程安全集合类。

https://www.jianshu.com/p/5dbaa6707017

https://www.cnblogs.com/jajian/p/10385377.html#autoid-1-7-0

(6) 你一般用什么作为HashMap的key?

• 健可以为Null值么?

• 一般用什么作为HashMap的key?

• 用可变类当HashMap的key有什么问题?

健可以为Null值么?

可以,key为null的时候,hash算法最后的值以0来计算,也就是放在数组的第一个位置。

具体看上文中的hash源码

一般用什么作为HashMap的key?

一般用Integer、String这种不可变类当HashMap当key,而且String最为常用。

• (1) 因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

• (2) 因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的,这些类已经很规范的覆写了hashCode()以及equals()方法。

用可变类当HashMap的key有什么问题?

hashcode可能发生改变,导致put进去的值,无法get出,如下所示

HashMap<List<String>, Object> changeMap = new HashMap<>();
List<String> list = new ArrayList<>();
list.add("hello");
Object objectValue = new Object();
changeMap.put(list, objectValue);
System.out.println(changeMap.get(list));
list.add("hello world"); // hashcode发生了改变
System.out.println(changeMap.get(list));

输出结果如下

java.lang.Object@33909752
null

实现一个自定义的class作为HashMap的key该如何实现?

此题考察两个知识点,这里就不展开了

• 重写hashcode和equals方法需要注意什么

• 如何设计一个不变类

HashMap面试总结的更多相关文章

  1. 记一次HashMap面试

    记一次HashMap面试 从网上已经身边同事朋友的面试情况来看,面试HashMap几乎是必问的,网上也很多类似的文章,但是真面起来,发现还是有很多点可以深抠的.本篇就结合一次面试经历说一下之前没有注意 ...

  2. 从HashMap面试聊聊互联网内卷

    微信公众号:大黄奔跑 关注我,可了解更多有趣的面试相关问题. 写在之前 毫无疑问,回想2020年有什么词出现在眼前最多的,无疑是"996"和"内卷",从马老师的 ...

  3. HashMap面试知识点

    HashMap的工作原理是近年来常见的Java面试题. 几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hashtable和HashMap之间的区别,那么为何这道面试题如 ...

  4. 你不得不知道的HashMap面试连环炮

    为什么用HashMap? 简述一下Map类继承关系? 解决哈希冲突的方法? 为什么HashMap线程不安全? resize机制? HashMap的工作原理是什么? 有什么方法可以减少碰撞? HashM ...

  5. HashMap面试知识点总结

    主要参考 JavaGuide 和 敖丙 的文章, 其中也有参考其他的文章, 但忘记保存链接了, 文中图片也是引用别的大佬的, 请见谅. 新手上路, 若有问题, 欢迎指正. 背景 HashMap 的相关 ...

  6. 有关 HashMap 面试会问的一切

    前言 HashMap 是无论在工作还是面试中都非常常见常考的数据结构. 比如 Leetcode 第一题 Two Sum 的某种变种的最优解就是需要用到 HashMap 的,高频考题 LRU Cache ...

  7. 一万三千字的HashMap面试必问知识点详解

    目录 概论 Hasmap 的继承关系 hashmap 的原理 解决Hash冲突的方法 开放定址法 再哈希法 链地址法 建立公共溢出区 hashmap 最终的形态 Hashmap 的返回值 HashMa ...

  8. HashMap面试必问的数据结构相关知识

    如果在看这篇文章时,对HashMap的结构还不是很了解,建议你参考前段时间写的<刨死你系列——HashMap剖析(基于jdk1.8)>,可能会对下面的提及到知识点有些帮助. 1:HashM ...

  9. HashMap面试必问的6个点,你知道几个?

    一.HashMap的实现原理? 此题可以组成如下连环炮来问 你看过HashMap源码嘛,知道原理嘛? 为什么用数组+链表? hash冲突你还知道哪些解决办法? 我用LinkedList代替数组结构可以 ...

随机推荐

  1. Ubuntu 更新源 内核升级

    cat /etc/apt/sources.listdeb http://mirrors.sohu.com/ubuntu/ precise main restricted universe multiv ...

  2. 使用线程池测试cpu的并发计算能力

    接到一个需求是测试一下cpu并发计算能力,针对int和float求和单位时间能执行几次的问题.可能是服务器选型用到的参数. 开始使用的是fork-join,但是发现fork-join每次得到的结果值波 ...

  3. Linq----------if使用

    static void Main(string[] args) { "; "; var processid = "c8b79051249940acbeca5dd951d2 ...

  4. ucosiii 移植

    最近想在 f429 上面使用 mdk526 版本的 IDE,配合 HAL 和ucosiii.考虑到的方法是对比 v7 开发板的 ucosiii 和裸机程序,找出需要修改的地方,然后对比 v6 开发板的 ...

  5. Spring加载Properties配置文件的三种方式

    一.通过 context:property-placeholder 标签实现配置文件加载 1) 用法: 1.在spring.xml配置文件中添加标签 <context:property-plac ...

  6. 转载-- SQL连接查询2 外连接(左右联接查询)

    http://www.cnblogs.com/zhangqs008/archive/2010/07/02/2341196.html 外连接主要包括左连接.右连接和完整外部连接. 1)左连接:Left ...

  7. nodejs 杂七杂八

    nodejs => 提供核心模块语法 node中的回调函数 都是异步

  8. Linux下部署开源版“禅道”项目管理系统《转载》

    Linux下部署开源版“禅道”项目管理系统 https://www.cnblogs.com/xxsl/p/6525378.html

  9. 九十三、SAP中ALV事件之七,对自己定义的工具栏进行添加和删改

    一.我们来到工具栏页面,如果不想要某个工具栏,删掉相应的文字再双击空白就可以了 二.我们添加一个工具栏,如ZADD,双击文字 三.保存静态文本,会弹出一个功能文本框 四.填写相应的内容后,点击对勾保存 ...

  10. HDU 4915 多校5 Parenthese sequence

    比赛的时候想了一个自认为对的方法,WA到死,然后还一直敲下去,一直到晚上才想到反例 找是否存在解比较好找,这种左右括号序列,把(当成1,把)当成-1,然后从前往后扫,+1或者-1 遇到?就当初(,然后 ...