HashMap面试题,看这一篇就够了!

在程序员这一职业中,集合是我们使用频率相当高的一个工具,而其中的 HashMap,则更是我们用以处理业务逻辑的好帮手,同时 HashMap 的底层实现和原理,也成了面试题中的常客。

还在担心面试中被问到 HashMap、HashTable 等知识点吗?还在为不知道 HashMap 的底层实现原理而不自信吗?相信你看完这篇文章,一定会对 HashMap 有更深刻的认识。

本场 Chat 中,主要介绍 HashMap 的如下内容:

  • JDK7、JDK8 中 HashMap 的底层实现原理、扩容机制以及异同点;
  • HashMap、HashTable 是什么关系?
  • HashMap 的线程不安全以及潜在的风险;
  • 如何规避 HashMap 的线程不安全?

适合人群:准备 Java 面试者、初中级 Java 开发者

更多技术文章,欢迎关注我的微信公众号:码不停蹄的小鼠松(微信号:busy_squirrel),也可扫下方二维码关注获取最新文章哦~

20191006125707747.png

文章目录:@

序言

在后端的日常开发工作中,集合是使用频率相当高的一个工具,而其中的HashMap,则更是我们用以处理业务逻辑的好帮手,同时HashMap的底层实现和原理,也成了面试题中的常客。

以前曾有详细了解过HashMap的实现原理,看过源码(JDK7 版本)。但随着 jdk 版本的飞速迭代(现在都到 JDK13 了,但新特性还从没用过。。),主流的 jdk 使用版本也终于从 JDK7 挪到了 JDK8。

由于 JDK 的向前兼容,在 JDK8 的使用过程中也没发现HashMap有什么特别之处,特性并无变化(依然线程不安全)。但最近的一次好奇心驱使,从 IDE 中点进去看了下HashMapput()方法,有点儿懵逼,怎么跟我记忆中的不太一样?从 JDK7 到 JDK8,HashMap也做了升级么?升级了什么哪些内容?

借着这股好奇心,把 JDK7 和 JDK8 的源码都翻了翻,对两者的实现原理做一下对比,JDK 版本都在半年左右一次的速度推陈出新,我们的认知当然也要跟上,不断学习,站在浪潮之巅,不然就要被这滚滚的信息泥石流给裹挟淹没了。

先展示下 Map 家族的关系层级,有助于我们更好的理解后面的内容。在这里插入图片描述

HashMap的基本知识点介绍就不多啰嗦了,直奔主题,看 JDK7 和 JDK8 的功能实现吧。

一、JDK7 中的 HashMap 底层实现

1.1 基础知识

不管是 1.7,还是 1.8,HashMap 的实现框架都是哈希表 + 链表的组合方式。结构图如下:在这里插入图片描述平常使用最多的就是put()get()操作,想要了解底层实现,最直接的就是从put()/get()方法看起。不过在具体看源码前,我们先关注几个域变量,打打基础,如下:在这里插入图片描述上图中,已对各个变量做了简单的解释。再多说一下,最后一个变量modCount,记录了 map 新增/删除 k-v 对,或者内部结构做了调整的次数,其主要作用,是对 Map 的iterator()操作做一致性校验,如果在 iterator 操作的过程中,map 的数值有修改,直接抛出ConcurrentModificationException异常。

还需要说明的是,上面的域变量中存在一个等式:

threshold = table.length * loadFactor;

当执行put()操作放入一个新的值时,如果 map 中已经存在对应的 key,则作替换即可,若不存在,则会首先判断size>=threshold是否成立,这是决定哈希 table 是否扩容的重要因素。

就使用层面来说,用的最多的莫过于put()方法、get()方法。想要详细了解运作原理,那就先从这两个方法看起吧,这两个方法弄明白了,也就基本能理清 HashMap 的实现原理了。

1.2 put()方法

当了解了以上的变量和用途后,接下来看下put()方法的具体实现:在这里插入图片描述如上面的截图代码所示,整个 put 方法的处理过程,可拆分为四部分:

  • part1:特殊 key 值处理,key 为 null;
  • part2:计算 table 中目标 bucket 的下标;
  • part3:指定目标 bucket,遍历 Entry 结点链表,若找到 key 相同的 Entry 结点,则做替换;
  • part4:若未找到目标 Entry 结点,则新增一个 Entry 结点。

不知大家有没有发现,上面截图中的put()方法是有返回值的,场景区分如下:

  • 场景 1:若执行 put 操作前,key 已经存在,那么在执行 put 操作时,会使用本次的新 value 值来覆盖前一次的旧 value 值,返回的就是旧 value 值;
  • 场景 2:若 key 不存在,则返回 null 值。

下面对 put 方法的各部分做详细的拆解分析。

1.2.1 特殊 key 值处理

特殊 key 值,指的就是 key 为 null。先说结论:
a) HashMap 中,是允许 key、value 都为 null 的,且 key 为 null 只存一份,多次存储会将旧 value 值覆盖;
b) key 为 null 的存储位置,都统一放在下标为0的 bucket,即:table[0]位置的链表;
c) 如果是第一次对 key=null 做 put 操作,将会在 table[0]的位置新增一个 Entry 结点,使用头插法做链表插入。

上代码:

private V putForNullKey(V value) {    for (Entry<K,V> e = table[0]; e != null; e = e.next) {        if (e.key == null) {            V oldValue = e.value;            e.value = value;            e.recordAccess(this);            return oldValue;        }    }    modCount++;    addEntry(0, null, value, 0);    return null;}/** * Adds a new entry with the specified key, value and hash code to * the specified bucket.  It is the responsibility of this * method to resize the table if appropriate. * * Subclass overrides this to alter the behavior of put method. */void addEntry(int hash, K key, V value, int bucketIndex) {    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);}/** * Like addEntry except that this version is used when creating entries * as part of Map construction or "pseudo-construction" (cloning, * deserialization).  This version needn't worry about resizing the table. * * Subclass overrides this to alter the behavior of HashMap(Map), * clone, and readObject. */void createEntry(int hash, K key, V value, int bucketIndex) {    Entry<K,V> e = ta
  • 3
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值