Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析

这篇具有很好参考价值的文章主要介绍了Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析,Java,java,面试,开发语言

前言

List、Set、HashMap作为Java中常用的集合,需要深入认识其原理和特性。

本篇博客介绍常见的关于Java中HashMap集合的面试问题,结合源码分析题目背后的知识点。

关于List的博客文章如下:

  • Java进阶(List)——面试时List常见问题解读 & 结合源码分析

关于的Set的博客文章如下:

  • Java进阶(Set)——面试时Set常见问题解读 & 结合源码分析

其他关于HaseMap的文章如下:

  • Java学数据结构(3)——树Tree & B树 & 红黑树 & Java标准库中的集合Set与映射Map & 使用多个映射Map的案例
  • Java学数据结构(4)——散列表Hash table & 散列函数 & 哈希冲突

引出


1.jdk1.7 HashMap:数组+单向链表;
2.jdk1.8 HashMap:数组+链表(单向)+红黑树;
3.当链表节点的数量达到8个时,通过treeify转为红黑树;
4.首次添加元素,初始容量16,大于16时,双倍扩容;
5.HashMap设置长度,第一个2的幂次方的值;
6.红黑树元素的高位或者低位节点个数<6时,那么就调用untreeify方法来退回链表结构;
7.jdk1.7采用的是头插法,即新来元素在链表起始的位置,而jdk1.8采用尾插法,可以有效的避免在多线程操作中产生死循环;
8.ConcurrentHashMap高并发线程安全;

核心:键值对,KEY不可重复,VALUE可以重复

HashMap底层结构是什么?

JDK 1.7和1.8 对比

jdk1.7 HashMap:数组+单向链表

jdk1.8 HashMap:数组+链表(单向)+红黑树

源码Node类

源码可以看到HashMap内部定义了静态Node类,Node类中成员有

Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析,Java,java,面试,开发语言

Node<K,V> next;

同样可以看到HashMap内部定义了静态TreeNode类,TreeNode类中成员有

Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析,Java,java,面试,开发语言

TreeNode<K,V> left;
TreeNode<K,V> right;

可以看出存在红黑树

而TreeNode继承了 LinkedHashMap.Entry,点进查看,可以看到 Entry也继承了 HashMap.Node。所以,TreeNode红黑树是从链表Node中转换过来的

Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析,Java,java,面试,开发语言

整体图:

Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析,Java,java,面试,开发语言

HashMap如何解决Hash碰撞问题?HashMap 何时从单链表,转换为红黑树?

  1. 首先理解什么是hash碰撞问题,HashMap存放的元素的KEY需要经过hash计算得到hash值,而这个hash值可以理解为就是此元素要存放的位置,即数组中的下标;但是如果两个不同元素经过hash计算后,得到的hash值相同时,即两个元素要存放的位置为同一个位置,产生冲突,这种现象就叫做hash碰撞。
  2. 要想了解HashMap是如何解决hash碰撞的,那我们就需要看看HashMap的put方法的源码中的核心操作

Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析,Java,java,面试,开发语言

 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
     //添加第一个元素时,会进入这个if结构,table为null,则第一次初始化这个table数组的长度为16
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
     //判断添加的元素的KEY经过hash计算得到的下标位置是否为null
        if ((p = tab[i = (n - 1) & hash]) == null)
            //如果是null,则直接添加元素
            tab[i] = newNode(hash, key, value, null);
     //不为null的情况
        else {
            Node<K,V> e; K k;
            //如果key相同,则用新的元素添加到旧元素的位置,后续会将就元素返回
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //判断是否为红黑树节点,如果是红黑树节点,则添加为红黑树
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            //链表类型
            else {
                //通过for循环遍历链表节点
                for (int binCount = 0; ; ++binCount) {
                    //如果链表节点next节点为空
                    if ((e = p.next) == null) {
                        //则添加至链表的next节点属性中
                        p.next = newNode(hash, key, value, null);
                        //如果链表节点 >= 7 说明链表存在8个已存的元素节点
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            //转为红黑树方法
                            treeifyBin(tab, hash);
                        break;
                    }
                    //如果KEY相同,匹配其他API 如 putIfAbsent()
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //存入新值,返回旧值
             if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
 ....

当链表节点的数量达到8个时,通过treeify转为红黑树

总结:HashMap是通过3层 if 结构来判断,数组下标位置是否有元素和下标位置的类型是链表还是红黑树然后通过链表和红黑树来解决hash碰撞的问题,当链表节点>=7时(当链表节点的数量达到8个时),会通过treeify转为红黑树

HashMap的扩容机制?HashMap的数组何时需要扩容?

1.首次添加元素

HashMap在第一次添加元素时,会进入第一个if结构来初始化数组的长度

Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析,Java,java,面试,开发语言

2.初始容量16

//添加第一个元素时,会进入这个if结构,table为null,则第一次初始化这个table数组的长度为16
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析,Java,java,面试,开发语言

3.大于16时,双倍扩容

此处resize方法就是扩容方法,jdk8中,resize方法除了扩容还增加了初始化的功能,进入此方法我们可以看一下源码

Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析,Java,java,面试,开发语言

 final Node<K,V>[] resize() {
     Node<K,V>[] oldTab = table;
     int oldCap = (oldTab == null) ? 0 : oldTab.length;
     int oldThr = threshold;
     int newCap, newThr = 0;
     if (oldCap > 0) {
         //如果当前数组的长度>=最大值(2^30)时,将预值threshold设置为最大值
         if (oldCap >= MAXIMUM_CAPACITY) {
             threshold = Integer.MAX_VALUE;
             return oldTab;
         }
         //如果当前数组的长度>=默认的初始长度16,则双倍扩容
         else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                  oldCap >= DEFAULT_INITIAL_CAPACITY)
             newThr = oldThr << 1; // double threshold
     }
     ...

调用resize方法的地方

Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析,Java,java,面试,开发语言

      final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {    
          ...
              ++modCount;
          if (++size > threshold)
              resize();
          afterNodeInsertion(evict);
          return null;

总结分析

  1. 从上面可以看出,HashMap在完成put元素存储后,会判断++size是否>了阈值,如果是就会去扩容
  2. 下面这个方法是在,put元素为链表节点,并且要转为红黑树时,会调用该方法,该方法会在一开始就判断是否需要扩容

Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析,Java,java,面试,开发语言

 final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
  1. 判断扩容的核心就是threshold这个值

Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析,Java,java,面试,开发语言

  1. 从resize方法中看到,HashMap在扩容时,是之前的双倍扩容

HashMap设置长度为11,那么数组的容量为多少?

第一个2的幂次方的值

指定了长度初始化HashMap时,它会将数组的容量经过一系列算法,设置为大于我们自定义值的第一个2的幂次方的值,

即 设置为11 , 则为2^4=16

Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析,Java,java,面试,开发语言

Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析,Java,java,面试,开发语言

 static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

HashMap 何时从红黑树转换为 单链模式?

Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析,Java,java,面试,开发语言

  1. HashMap在resize方法执行时,会将元素从旧数组转入新数组,此时如果转移元素为红黑树结构,那么就会调用split方法来分割红黑树方便转移
  2. split方法内部,在分割时,会生成高位树与低位树两种此时也会进行判断如果红黑树元素的高位或者低位节点个数<6时,那么就调用untreeify方法来退回链表结构

split方法和untreeify方法

Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析,Java,java,面试,开发语言

final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
    ...        //lowhead   低位树
        if (loHead != null) {
            //在红黑树节点元素往新数组中添加时,会调用split方法来重组这个红黑树
            //此时会判断,红黑树的节点操作次数是否<6,即low树(低位树的节点数)< 6时,会通过untreeify方法来退化为链表
            if (lc <= UNTREEIFY_THRESHOLD)
                tab[index] = loHead.untreeify(map);
            else {
                tab[index] = loHead;
                if (hiHead != null) // (else is already treeified)
                    loHead.treeify(tab);
            }
        }
        //此时会判断,红黑树的节点操作次数是否<6,即high树(高位树的节点数)< 6时,会通过untreeify方法来退化为链表
        //highhead  高位树 
        if (hiHead != null) {
            if (hc <= UNTREEIFY_THRESHOLD)
                tab[index + bit] = hiHead.untreeify(map);
            else {
                tab[index + bit] = hiHead;
                if (loHead != null)
                    hiHead.treeify(tab);
            }
        }
}

Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析,Java,java,面试,开发语言

HashMap 为什么在多线程并发使用过程中,容易造成死循环/死锁?

图示:

Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析,Java,java,面试,开发语言

  1. 产生死循环的核心原因是因为,jdk1.7采用的是头插法,即新来元素在链表起始的位置,而jdk1.8采用尾插法,可以有效的避免在多线程操作中产生以上死循环
  2. 但是HashMap不是线程安全的,所以在多线程的场景中,虽然不会出现死锁/死循环,但是还是会出现节点丢失的情况,所以在并发的场景中推荐使用ConcurrentHashMap

如何得到一个线程安全的HashMap集合?

Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析,Java,java,面试,开发语言

ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap<>();

总结

1.jdk1.7 HashMap:数组+单向链表;
2.jdk1.8 HashMap:数组+链表(单向)+红黑树;
3.当链表节点的数量达到8个时,通过treeify转为红黑树;
4.首次添加元素,初始容量16,大于16时,双倍扩容;
5.HashMap设置长度,第一个2的幂次方的值;
6.红黑树元素的高位或者低位节点个数<6时,那么就调用untreeify方法来退回链表结构;
7.jdk1.7采用的是头插法,即新来元素在链表起始的位置,而jdk1.8采用尾插法,可以有效的避免在多线程操作中产生死循环;
8.ConcurrentHashMap高并发线程安全;文章来源地址https://www.toymoban.com/news/detail-715422.html

到了这里,关于Java进阶(HashMap)——面试时HashMap常见问题解读 & 结合源码分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • JavaEE 面试常见问题

    JavaEE 面试常见问题

    Mybatis 是一种典型的半自动的 ORM 框架,所谓的半自动,是因为还需要手动的写 SQL 语句,再由框架根据 SQL 及 传入数据来组装为要执行的 SQL 。其优点为: 1. 因为由程序员自己写 SQL ,相对来说学习门槛更低,更容易入门。 2. 更方便做 SQL 的性能优化及维护。 3. 对关系型数据

    2024年02月14日
    浏览(13)
  • 面试-Dubbo常见问题

    Dubbo 是一个RPC框架,包含注册中心,服务提供方,服务消费方,控制台,监控中心。 Dubbo 启动时会从注册中心拉取消费者需要的提供方信息,如果依赖的服务提供方不可用,Dubbo消费方会启动失败,并且不停的向注册中心请求提供方信息,抛出异常找不到对应的提供方。可以

    2024年02月08日
    浏览(8)
  • 单片机面试常见问题

    1、中断的概念?简述中断的过程 (1)中断:指CPU在正常执行程序的过程中,由于内部/外部事件的触发或由程序的预先安排,引起CPU暂时中断当前正在运行的程序,转而执行为内部/外部事件或程序预先安排的事件的服务子程序,待中断服务子程序执行完毕后,CPU再返回到被

    2024年04月10日
    浏览(11)
  • JVM基础,面试常见问题

    JVM基础,面试常见问题

    目录 一.运行时数据区域 1.线程独享 (1)栈 (2)程序计数器 2.线程共享 (1)方法区 (2)堆 二.内存如何分配 1.指针碰撞法 2.空闲列表法 3.TLAB 三.对象在内存中的组成 1.对象头 (1)markword (2)指向类型的指针 (3)如果是数组-》数组长度 2.实例数据 3.对齐填充 四.如何访

    2024年01月23日
    浏览(10)
  • 【数据结构面试常见问题】

    【数据结构面试常见问题】

    数据结构作为计算机的一门基础学科,它在面试中占有很大的比重,本科阶段,我们也学过数据结构与算法,内容比较多,也比较难,尤其是图的应用以及各类查找和排序算法,这些也都是核心内容。数据结构在实际的应用中也比较多,因此,整理一些常见的笔试、面试的数

    2024年03月22日
    浏览(5)
  • 项目经理岗面试常见问题

    一、注意事项   ·电面邀约确认(避免hr刷KPI): 请问贵司招聘的是什么岗位,是新建团队还是原有团队? 这边面试流程是怎样的,是 leader 直接面,还是?   ·面试前铺垫: 如果您对某部分感兴趣,请随时打断我。   ·面试中发挥: 尽量采用 STAR 原则回答,即 情境( Si

    2024年02月05日
    浏览(12)
  • 大数据常见面试问题汇总

    目录 第1章 核心技术 1.1 LinuxShell 1.1.1 Linux常用高级命令 1.1.2 Shell常用工具及写过的脚本 1.1.3 Shell中单引号和双引号区别 1.2 Hadoop 1.2.1 Hadoop常用端口号 1.2.2 HDFS读流程和写流程 1.2.3 HDFS小文件处理 1.2.4 HDFS的NameNode内存 1.2.5 Shuffle及优化 1.2.6 Yarn工作机制 1.2.7 Yarn调度器 1.2.8 HDFS块大

    2024年02月14日
    浏览(11)
  • List常见面试问题

    List常见面试问题

    Java中的List是一种存放有序的、可以重复的数据的集合,它允许重复元素的存在。List中的元素都有对应的一个序列号(索引)记录着元素的位置,因此可以通过这个序列号来访问元素。 ‍ Java中的List有三种实现方式:ArrayList、LinkedList和Vector。其中,ArrayList是基于数组实现的,

    2024年02月09日
    浏览(6)
  • kubernetes常见面试问题详解

    kubernetes常见面试问题详解

    在面试的时候,面试官常常会问一些问题: k8s是什么?有什么用? k8s由哪些组件组成? pod的启动流程? k8s里有哪些控制器? k8s的调度器里有哪些调度算法? pod和pod之间的通信过程? 外面用户访问pod数据流程? 你常用哪些命令? 容器的类型? 3种探针? pod的升级? HPA、V

    2024年02月10日
    浏览(9)
  • docker常见面试问题详解

    docker常见面试问题详解

    在面试的时候,面试官常常会问一些问题: docker是什么,能做什么? docker和虚拟机的区别是什么呢? docker是用什么做隔离的? docke的网络类型?docker数据之间是如何通信的? docker的数据保存问题? 常用的docker命令? docker制作镜像相关? 下面,就让我来详细说明一些这些问

    2024年02月10日
    浏览(12)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包