HashMap的面试题

2023-11-07

目录

1、底层数据结构 1.7和1.8有何不同

2、为什么用红黑树,为何不一上来就树化,树化阈值为何是8,何时会树化,何时会退化为链表

3、索引如何计算?hashCode都有了,为何还要提供hash()方法?数组容量为何是2的n次幂

4、HashMap的put方法流程 1.7和1.8有何不同

5、加载因子为何默认是0.75

6、多线程下操作HashMap(线程不安全的)会有什么问题

7、key是否能为null,作为key的对象有什么要求?

8、String对象的hashCode()如何设计的,为啥每次乘的是31


1、底层数据结构 1.7和1.8有何不同

1.7 数组+链表

1.8 数组+链表+红黑树

2、为什么用红黑树,为何不一上来就树化,树化阈值为何是8,何时会树化,何时会退化为链表

① 红黑树是用来避免DOS攻击,防止链表超长时性能下降,树化应该是偶然情况 1.hash表的查询,时间复杂度是O(1),而红黑树的查找,时间复杂度是O(log2(n)),并且树化占用的空间也普遍比链表大,如非必要,尽量还是使用链表 2.hash值如果只够水机,则在hash表内按照泊松分布,在负载因子是0.75的情况下,长度超过8的链表出现的概率是0.00000006,选择8就是为了让树化的几率足够小

② 树化的两个条件 链表长度大于8 数组容量大于等于64

③ 退化链表的两种情况 1.在扩容的时候,如果拆分树时,树元素个数<=6 则会退化链表(如果是7的话,会频繁进行红黑树和链表的转换,影响性能,所以退了一位,使得不那么容易变回红黑树) 2.remove树结点的时候,如果root,root.left,root.right,root.left.left有一个为null,也会退化为链表

3、索引如何计算?hashCode都有了,为何还要提供hash()方法?数组容量为何是2的n次幂

① 首先计算对象的hashCode(),在调用HashMap的hash()方法进行二次哈希,最后 &(capacity-1)得到索引

// key就是hashCode()的值
  public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
  //hash(key) 是进行二次哈希
  static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
  // n是capacity 数组容量   hahs是二次哈希得到的值
   p = tab[i = (n - 1) & hash]

② 二次哈希 hash() 是为了综合高位数据,让哈希分布更均匀,防止哈希冲突,使得计算索引的时候,出现相同的概率降低防止出现链表过长的情况

1. HashMap容量较小而hash值比较大的时候,哈希冲突容易变多

2. 假设容量为16,那么二进制(0000 1111)进行按位与操作,hash值的高28位不会参与(因为0000 1111前的28位都是0,32位,其中4位符号),所以哈希冲突就会变多

3. 进行右移获取的hash值就会让二进制的高位尽可能多地参与按位与操作,从而减少哈希冲突

③ 计算索引的时候 如果是2的n次幂

好处

1.可以使用位运算代替求模运行

(求索引位置是 hash&容量 变成(容量-1)& hash), 因为 &的效率是大于/

2.扩容的时候hash& oldCap == 0 元素就留在原来位置

否则新位置= 旧位置 + oldCap

//扩容的方法中  resize()中 每次都判断
if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                    else { // preserve order //维持排序
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            //判断hash& oldCap == 0
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            //否则
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        //hash& oldCap == 0 loTail 不等于空
                        //保持原来位置
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        //否则hiTail 不等于空  新位置 = 原来位置+oldCap(原来的容量)
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }

缺点

当例如都是偶数的时候,在如何摸容量 都索引位置都不是奇数,造成哈希表分布性不好

因此 如果追求性能就使用2的n次幂 如果追求哈希表的分布性就使用质数容量

①②③都是配合容量为2的n次幂的优化手段,但是并不能说那种设计更优,应该是设计者综合了各种因素,最终选择了2的n次幂作为容量(HashTable就是不是2的n次幂 扩容长度原来二倍+1)

4、HashMap的put方法流程 1.7和1.8有何不同

  1. HashMap是懒惰创建数组的,首次使用put方法才创建数组(创建对象的时候数组是为空的)

  2. 计算索引(桶下标)

  3. 如果桶的下标还没人占用,创建Node占位返回

  4. 如果桶下标已经有人占用了

    1. 已经是红黑树走红黑树的添加或者更新逻辑

    2. 普通的链表,走链表的添加或者更新逻辑,如果链表长度超过树化的阈值(8) ,数组容量大于等于64就进行树化逻辑

    3. 其中添加和更新逻辑,是看equals是否相等,相等就是更新逻辑,不相等就是添加逻辑

  5. 返回前检查容量是否超过阈值(容量*0.75),一旦超过进行扩容

  6. 不同

    1. 链表的插入节点,1.7是头插法,1.8是尾插法

    2. 1.7是大于等于阈值且没有空位(就是计算的索引位置有元素)的时候才扩容,而1.8是大于阈值就扩容

    3. 1.8扩容计算Node索引的时候,会优化

5、加载因子为何默认是0.75

  1. 在空间占用与查询时间之间取得较好的权衡

  2. 大于这个值,空间节省了,但是链表长度就会比较长影响性能

  3. 小于这个值,冲突减少了,但是扩容就会更加频繁,空间占用多

6、多线程下操作HashMap(线程不安全的)会有什么问题

①扩容死链(1.7)

多线程的情况会出现这种死链的情况

数组是固定长度,链表太长就需要扩充数组长度进行rehash减少链表长度。如果两个线程同时触发扩容,在移动节点时会导致一个链表中的2个节点相互引用,从而生成环链表

 

②数据错乱(1.7 1.8)

当进行添加的时候

 

线程1添加 a 取得索引为1 线程2添加1 取得索引也为1

例如线程1 进入到630行 执行为null,准备执行631的时候

线程2 进入630行执行也为null 执行631行,结束后 数组索引为1的位置元素为1

然后线程1这个时候已经if 判断完毕了,也执行631行,这个时候就将a放入到数组索引为1的位置

最终出现数据的丢失问题

7、key是否能为null,作为key的对象有什么要求?

  1. HashMap的key可以为null,但是Map的其他实现则不然,会出现空指针异常

  2. 作为key对象,必须实现hashCode和equals ,并且key的内容不能修改(不可变)

    否则修改后hashCode改变了,索引位置改变了,就会出现不同的情况

8、String对象的hashCode()如何设计的,为啥每次乘的是31

public int hashCode() {
        int var1 = this.hash;
        if (var1 == 0 && this.value.length > 0) {
            char[] var2 = this.value;

            for(int var3 = 0; var3 < this.value.length; ++var3) {
                var1 = 31 * var1 + var2[var3];
            }

            this.hash = var1;
        }

        return var1;
    }

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

HashMap的面试题 的相关文章

  • 区块链智能合约开发学习

    最近正在肝区块链知识学习 入手学习智能合约的开发 由于网上资料实在是太少了 好不容易东拼西凑完成了智能合约的开发 编译 部署 web3js调用 网页页面 和web3j调用 java调用 赶紧趁热把重点提炼出来 先上图 是我最近学习知识点的一
  • cout和cerr的区别

    问题 c 中输出通常信息的函数为cout 比如 std cout lt lt Hello world 在异常处理机制中则使用cerr来输出错误信息 比如 std cerr lt lt Error too many arguments n 那
  • 美女程序员访谈:IT因你而美丽

    如今的计算机界是个以男性为主的领域 但可不要因为数量对比悬殊就忽视了女性的存在 无论第一位程序员还是第一个Bug捕手都是女性 在3月8日这个特别的日子 程序员 特别邀请了i Free中国分公司总经理王金星 原恒生电子项目主管 现正创业的刀刀

随机推荐

  • echarts图表数据刷新后label文字不变化的问题以及解决方案

    使用select切换数据 得到新的数据后给serise里的data赋值 会发现图表的数据是变了 但是后面的数值不变 数值是用series里的label显示的 图表的数据变成了60多 但是后面的值还是上一次的值381 找了很久的方法 网上有说
  • realityOS 出现在开发者开放源代码中,苹果眼镜要成真

    realityOS 出现在开发者开放源代码中 苹果眼镜要成真 据报道 苹果 公司正在研发一款新的混合现实头戴设备 预计将在今年某个时候发布 我们已经听到了很多关于这个产品的传言 但现在有了来自苹果公司的新证据证明了它的存在 这来自于苹果公司
  • 解决找不到android.support.v7.app.ActionBarActivity的类文件 问题

    遇到提示 android support v7 app ActionBarActivity is deprecated use AppCompatActivity instead 意思是 ActionBarActivity在最新版本的sup
  • 智地平线人工智能(ChatGPT&豆包&讯飞星火)实际使用体验

    引言 AIGC 即 人工智能生成内容 的缩写 代表着由人工智能生成的内容 此征文活动旨在探讨和展示人工智能在学术领域的应用 以及与人类创作者的合作 挑战和我们邀请所有对人工智能 创作和文化交流感兴趣的个人参与 共同探索这个充满创新的领域 近
  • BUG:RuntimeError: CUDA error: invalid device ordinal CUDA kernel errors might be asynchronously repo

    报错分析 当运行以下代码报错 self opt gpu ids 1 torch cuda set device self opt gpu ids 0 报错信息如下 RuntimeError CUDA error invalid device
  • Oracle学习笔记二

    多表查询 笛卡尔积 实际上是两张表的乘积 但是在实际开发中没有太大意义 格式 select from 表1 表2 select from emp select from dept select from emp dept select fr
  • 输入流输出流 读取的写入操作和案例

    1 输入输出流 在Java中 把不同类型的输入输出源抽象为流 其中输入和输出的数据称为数据流 数据流是Java程序发送和接收数据的一个通道 数据流中包括输入流和输出流 通常应用程序中使用输入流读出数据 输出流写入数据 流式输入 输出的特点是
  • Eclipse的两个JS插件安装及配置EXT支持

    JSEclipse 在线安装 JSEclipse是个Eclipse下的免费Javascript脚本编辑器 最大的特点就是对js的自动完成功能非常完美 在Eclipse中如何安装JSEclipse 在http www interaktonli
  • 详解union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=‘ ‘--+

    此文章是记录本人对知识理解的随手笔记 内容不肯定百分百正确 如有错误望指出并谅解 1 group concat函数是将查询到的每行结果以某个字段名进行合并 每一行合并的结果以逗号分隔开 可以结合以下例子理解 下图是没使用group conc
  • gym 自己环境搭建

    http t csdn cn ILs89http t csdn cn ILs89发现gym envs文件夹下的 init 可以不加from import 只需要注册相应文件夹下的自己的环境就好 值得注意的 需要指定好文件路径 gym env
  • 20230816 图像处理

    1 2022 图像检索资料总结 知乎 2 高斯金字塔与拉普拉斯金字塔的原理与python构建 3 一篇文章为你讲透双线性插值 知乎 4 图像处理基础 图像滤波 知乎 5 高斯分布的应用 高斯分布实际用途 又要起名字了的博客 CSDN博客 6
  • Android 实现护眼模式

    一 背景 在阅读软件或者儿童软件都需要护眼模式来降低蓝光的辐射 二 实现方案 首先在每个activity创建的时候在最上层添加一层view 去掉点击事件 用sp或者mmkv来存储当前是否打开护眼模式 在每次activity onresume
  • 二.基于nodejs express multer 上传图片功能实现+详细说明_番茄出品

    START 前几天熬夜做了一个基于nodejs的后端服务 连接mysql数据库搞定了 但是最近遇到上传图片一个需求 这如何实现呢 别着急 番茄带你一点一点实现 本文作者 lazy tomato 编写时间 2022 03 31 22 21 前
  • Linux教程:YUM与开源项目实战(Web运维)

    1 了解Linux软件的安装方式 2 掌握更新yum源 3 掌握YUM软件安装方式 4 了解LAMP环境以及AMP的关系 5 了解阿里云ECS的创建过程 6 能够yum方式搭建lamp环境 7 能够实现Discuz 论坛部署 8 能够购买域
  • Django入门教程

    Django入门教程 创建Django项目 django admin startproject mysite 这将在目录下生成一个mysite目录 也就是你的这个Django项目的根目录 它包含了一系列自动生成的目录和文件 具备各自专有的用
  • 聊聊FFT(二)----幅值、模值与分辨率

    以常见的家用交流220V 以下称AC220V 工频电信号为例 大家都知道家里的插座内有220V的电 可以给电饭锅 热水壶 空调冰箱等等电器供电 至于220V具体指的是什么可能非理工科背景的同学没有深究过 有效值又称 均方根值 一种用以计量交
  • 使用docker安装我们的ES启动时的异常解决

    一开始我启动失败 我一直是以为我们的内存大小分配的问题 es默认启动占用内存是2g docker run e ES JAVA OPTS Xms256m Xmx256m id p 1001 1001 p 9301 9301 v home es
  • CSDN周赛62期反馈及简要题解

    持续了十期的 计算之魂 主题周赛告一段落 可能上周就已经告一段落了 以致于也出现了重复的考题 这本书确实不错 里面提到的计算机思维我认为是理解和学习计算机科学的基础 第一次读此书的时候就一口气读到第八章 读到精彩之处 不禁拍案叫绝 在此也强
  • 三、Esp32引脚资源详细

    三 Esp32引脚资源详细 文章目录 三 Esp32引脚资源详细 3 1 仅输入引脚 3 2 SPI闪存 3 3 电容式触摸GPIO 3 4 模数转换器 ADC 3 5 数模转换器 DAC 3 6 RTC GPIOs 3 7 脉冲宽度调制
  • HashMap的面试题

    目录 1 底层数据结构 1 7和1 8有何不同 2 为什么用红黑树 为何不一上来就树化 树化阈值为何是8 何时会树化 何时会退化为链表 3 索引如何计算 hashCode都有了 为何还要提供hash 方法 数组容量为何是2的n次幂 4 Ha