HashMap总结使用+原理+面试

文章目录

      • 1.Hashmap的基本使用
        • 创建hashmap对象。
        • 遍历hashmap
        • 统计字母出现的次数用来投票计算
        • 返回JSON数据
      • 2.hashmap源码阅读
        • put源码阅读
      • 3. HashMap 面试题目
        • hashmap实现的原理
        • 什么时候数组需要进行扩容
        • hashmap怎么确定把数据放到那个节点的哪个位置。
        • 为什么用 `n - 1` 与运算?
        • 计算哈希值比较高效的哈希函数有哪些?
        • 是否是线程安全
        • 扩容机制
        • 负载因子为什么是0.75
        • HashMap 允许空键(`null` key)和空值(`null` value`)吗?

1.Hashmap的基本使用

创建hashmap对象。

创建一个hashmap对象, put方法进行往里面添加值。

Map<String,String> map=new HashMap<>();
map.put("111","abc");
map.put("112","abcd");
map.put("1123","abcde");
遍历hashmap
//直接遍历 keySet()
for (String key : map.keySet()) {
    System.out.println(key + " " + map.get(key));
}

//如果只需要遍历value
for (String str : map.values()) {
    System.out.println(str);
}
//表示键值对的 Entry
for (Map.Entry<String, String> entry : map.entrySet()) {
    String key = entry.getKey();
    String value = entry.getValue();
}

//stream流
map.entrySet().forEach(entry -> {
    System.out.println(entry.getKey() + " " + entry.getValue());
});
统计字母出现的次数用来投票计算

map集合的作用,统计字母出现的次数,比如有一串字母,想要统计每一个字母出现的次数可以使用map的结构。

public static void main(String[] args) {
    String str = "aaabbbcccdddeee";
    Map<Character,Integer> map=new HashMap<>();
    for (int i = 0; i < str.length(); i++) {
        char c=str.charAt(i);
        if (map.containsKey(c)){
            Integer value=map.get(c);
            map.put(c,value+1);
        }else{
            map.put(c,1);
        }
    }
    System.out.println(map);
}

返回JSON数据

在开发中map还可以用来存储数据,然后返回给前端。 如果后端返回的数据不固定,可以不设置VO类,直接用Map封装起来返回给前端,也是key-value的形式。

 Map<String, Object> map = new HashMap<>();
Integer id = selctedUser.getId();
        map.put("username", selctedUser.getUsername());
        map.put("id", id);
        String token = JWTUtil.createJWT("njitzx", 1000 * 3600 * 10, map);
        map.put("token", token);
@PostMapping("/login")
public Result login(@RequestBody User user) {
    Map<String, Object> map = userService.login(user);
    return Result.success(map);
}

2.hashmap源码阅读

hashmap的结构:

HashMap的成员变量:

transient Node<K,V>[] table; 哈希表结构中数组

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认容量是16

static final float DEFAULT_LOAD_FACTOR = 0.75f; //默认的加载因子 决定了hashmap的扩容时机.

hashmap每一个键值对对象中包含的元素:

链表中是Node

不是链表 TreeNode

hashmap创建的时候是懒加载创建,没有put的时候是空的。

直接创建一个空的HashMap,直接赋值因子,其他并没有创建。

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // 0.75
}
put源码阅读

put方法调用,里面调用了putval方法。

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
                                        //重复的数据覆盖
}

需要对key进行hash取值,放在数组的某个位置,这个hash函数就是为了分布均与一些,减少哈希碰撞。

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
h ^ (h >>> 16):然后将原哈希值 h 与其右移后的值做异或操作。异或操作的效果是将低 16 位和高 16 位的值混合,生成一个更均匀的哈希值。这有助于降低哈希碰撞的概率,尤其是在数据较多时,可以避免哈希值的分布过于集中。

putval方法

1.添加元素的时候,数组的位置为null.

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 ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length; //如果都为空 那么初始化这个数组

    i= (n - 1) & hash;  //计算索引的位置
    
    p = tab[i]
    if (p== null)  //hash值和n-l 与运算  不为空直接插入
        tab[i] = new Node(hash, key, value, null);  
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;  //添加的内容是相等的
        else if (p instanceof TreeNode) //p是否是树的节点
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            //如果不是红黑树中的节点  表示当前挂的是链表  按照链表的规则进行添加
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    //判断长度是否大于8   还有数组长度是否大于64 如果都满足 转为红黑树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;  //结束循环
                }
                  //出现了重复的内容  直接break
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;  //p往下移动一个   上面e=p.next  p=e;
            }
        }
        //p.next 复制给e了   当前需要覆盖元素的
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            // onlyIfAbsent 是false 覆盖的    oldvalue不为空
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;   
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

3. HashMap 面试题目

hashmap实现的原理

jdk1.7之前hashmap实现的原理 是数组+链表实现的。 Node[] tables;

jdk1.8之后,当数据量比较少的时候时数组+链表,数据量比价大的时候时 数组+红黑树。当链表长度大于8,一个桶的元素超过了8会讲该桶转为红黑树。 当总的数量大于64,并且桶内的链表长度大于8的时候,也会进行链表到红黑树转换。

什么时候数组需要进行扩容

HashMap 有一个负载因子(默认值是 0.75),它决定了何时扩容。负载因子表示数组已填充的程度,当数组中已有的元素数量超过数组容量与负载因子乘积时,HashMap 会进行扩容。

threshold 这个用来记录阈值的。扩容一般是两倍进行扩容。

hashmap怎么确定把数据放到那个节点的哪个位置。

对于每一个key就是hashi值,然后和n-1进行与运算确定节点存在哪个位置,然后如果是单链表还需要遍历单链表去找到这个节点存储的位置。

为什么用 n - 1 与运算?

当数组的大小是 2 的幂时,n - 1 的二进制表示是由多个 1 组成的,这样可以保证哈希值的低位能够正确地映射到数组的索引。例如,如果 n 为 16,那么 n - 1 为 15(二进制:1111)。通过 & 运算,只保留哈希值的低 4 位,可以均匀地将哈希值分布到数组的各个位置。

计算哈希值比较高效的哈希函数有哪些?

拿到hashcode之后和 h>>16 向右移动16位进行或运算,生成一个更加均匀的哈希值。

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
h ^ (h >>> 16):然后将原哈希值 h 与其右移后的值做异或操作。异或操作的效果是将低 16 位和高 16 位的值混合,生成一个更均匀的哈希值。这有助于降低哈希碰撞的概率,尤其是在数据较多时,可以避免哈希值的分布过于集中。
是否是线程安全

HashMap 不是线程安全的。在多线程环境下,如果多个线程同时访问并修改同一个 HashMap 实例,可能会导致数据不一致、死循环或者其他意外的行为 。

put 和 get 并发时会导致 get 到 null

HashMap 在扩容时,通常会执行以下步骤:

  1. 创建一个更大的数组(<font style="color:rgb(44,62,80);background-color:rgb(255,255,255);">newTab</font>),通常是原数组的两倍大。
  2. 将旧数组中的所有元素迁移到新数组中,这个过程是逐个元素搬迁的。
  3. 最后,<font style="color:rgb(44,62,80);background-color:rgb(255,255,255);">table = newTab</font> 将哈希表的引用指向新的数组。

在扩容过程中,可能会有多个线程同时访问 <font style="color:rgb(44,62,80);background-color:rgb(255,255,255);">HashMap</font>,其中一个线程(线程 1)正在执行扩容操作,将旧数组的数据迁移到新的数组上,而另一个线程(线程 2)则可能在此时进行 <font style="color:rgb(44,62,80);background-color:rgb(255,255,255);">get</font> 操作,导致其读取到不一致的数据(比如访问到了尚未迁移的部分或读取到了一个空的哈希表)。

不安全可以从原子性的角度去考虑,不是原子性的操作,不安全。

ConcurrentHashMap 是一种高性能的线程安全 Map 实现,适用于高并发场景。

它支持并发读取和部分更新(写操作是分段锁的),避免了全表锁定,提高了并发性能。

扩容机制

HashMap 的扩容是通过 resize 方法来实现的,该方法接收一个新的容量 newCapacity,然后将 HashMap 的容量扩大到 newCapacity:

  • 获取旧数组及容量:如果旧容量已经达到 HashMap 支持的最大容量 MAXIMUM_CAPACITY( 2 的 30 次方),就将新的阈值 threshold 调整为 Integer.MAX_VALUE(2 的 31 次方 - 1)
  • 创建新数组并转移元素:将旧数组 oldTable 中的元素转移到新数组 newTable 中。转移过程是通过调用 transfer 方法来实现的。该方法遍历旧数组中的每个桶,并将每个桶中的键值对重新计算哈希值后,将其插入到新数组对应的桶中。
  • 重新计算阈值 threshold:转移完成后,方法将 HashMap 内部的数组引用 table 指向新数组 newTable,并重新计算阈值 threshold:
负载因子为什么是0.75
  • 为了在时间和空间成本之间达到一个较好的平衡点,既可以保证哈希表的性能表现,又能够充分利用空间。
  • 如果负载因子过大,填充因子较多,那么哈希表中的元素就会越来越多地聚集在少数的桶中,这就导致了冲突的增加,这些冲突会导致查找、插入和删除操作的效率下降。
  • 如果负载因子过小,那么桶的数量会很多,虽然可以减少冲突,但会导致更频繁地扩容,在空间利用上面也会有浪费。
HashMap 允许空键(null key)和空值(null value`)吗?

允许一个 **null**HashMap 允许最多一个键为 null。这是因为 HashMap 使用 hash(null) 来计算其位置,通常会将 null 键存储在数组的第一个位置(索引为 0)。

如果key为null,那么就放在0的位置上面。

允许多个 **null**HashMap 允许多个键对应的值为 null。这意味着不同的键可以映射到 null 值,而不会相互干扰。

参考

https://blog.csdn.net/qq_49217297/article/details/126304736

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/949730.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JS中函数基础知识之查漏补缺(写给小白的学习笔记)

函数 函数是ECMAScript中 最有意思的部分之一, 主要是因为函数实际上是对象.-- 每个函数 都是Function类型的实例,Function也有属性和方法. 因为函数是对象,所以函数名就是指向函数对象的指针. 常用的定义函数的语法: ①函数声明 ②函数表达式 ③箭头函数 function sum (n…

Skyeye 云 VUE 版本 v3.15.3 发布,涉及 ERP、OA、财务等

Skyeye 云智能制造&#xff0c;采用 Springboot winUI 的低代码平台、移动端采用 UNI-APP。包含 30 多个应用模块、50 多种电子流程&#xff0c;CRM、PM、ERP、MES、ADM、EHR、笔记、知识库、项目、门店、商城、财务、多班次考勤、薪资、招聘、云售后、论坛、公告、问卷、报表…

LInux单机安装Redis

1. 安装gee工具包 由于Redis是基于c语言编写的所以安装的时候需要先安装gee以及gcc的依赖,yum云用不了可以看一下这个 linux 替换yum源镜像_更换yum镜像源-CSDN博客 yum install -y gcc tcl 2. 添加redis的压缩包 3. 上传到Linux 上传到 /usr/local/src 目录、这个目录一般用于…

热备份路由HSRP及配置案例

✍作者&#xff1a;柒烨带你飞 &#x1f4aa;格言&#xff1a;生活的情况越艰难&#xff0c;我越感到自己更坚强&#xff1b;我这个人走得很慢&#xff0c;但我从不后退。 &#x1f4dc;系列专栏&#xff1a;网路安全入门系列 目录 一&#xff0c;HSRP的相关概念二&#xff0c;…

java开发springoot

阅读理解 命令之间空一行&#xff1a;表示前面的是配置 红色背景&#xff1a;表示待验证蓝色背景&#xff1a;表示常用或推荐绿色背景&#xff1a;注意/推荐 json 转 对象 import com.fasterxml.jackson.databind.ObjectMapper; public DebangResp convertJsonToObject(Stri…

gesp(C++一级)(17)洛谷:B4062:[GESP202412 一级] 温度转换

gesp(C一级)&#xff08;17&#xff09;洛谷&#xff1a;B4062&#xff1a;[GESP202412 一级] 温度转换 题目描述 小杨最近学习了开尔文温度、摄氏温度和华氏温度的转换。令符号 K K K 表开尔文温度&#xff0c;符号 C C C 表摄氏温度&#xff0c;符号 F F F 表华氏温度&am…

windows ping ssh

问题解决1&#xff1a;局域网内&#xff0c;为啥别人ping不到我的IP 问题解决2&#xff1a;ssh连接windows10拒绝连接 第一步&#xff1a;ssh使用的22端口&#xff0c;首先确认windows10的22端口是否开启。 –开启步骤 1.控制面板–>Windws Defender 防火墙–>高级设置…

《Rust权威指南》学习笔记(二)

枚举enum 1.枚举的定义和使用如下图所示&#xff1a; 定义时还可以给枚举的成员指定数据类型&#xff0c;例如&#xff1a;enum IpAddr{V4(u8, u8, u8, u8),V6(String),}。枚举的变体都位于标识符的命名空间下&#xff0c;使用::进行分隔。 2.一个特殊的枚举Option&#xff0…

科研绘图系列:R语言单细胞数据常见的可视化图形

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包数据下载导入数据数据预处理图1图2图3图4图5图6系统信息参考介绍 单细胞数据常见的可视化图形 因为本教程是单细胞数据,因此运行本画图脚本需要电脑的内存最少32Gb 加载…

打造三甲医院人工智能矩阵新引擎(二):医学影像大模型篇--“火眼金睛”TransUNet

一、引言 1.1 研究背景与意义 在现代医疗领域,医学影像作为疾病诊断与治疗的关键依据,发挥着不可替代的作用。从传统的X射线、CT(计算机断层扫描)到MRI(磁共振成像)等先进技术,医学影像能够直观呈现人体内部结构,为医生提供丰富的诊断信息,涵盖疾病识别、病灶定位、…

IP查询于访问控制保护你我安全

IP地址查询 查询方法&#xff1a; 命令行工具&#xff1a; ①在Windows系统中&#xff0c;我们可以使用命令提示符&#xff08;WINR&#xff09;查询IP地址&#xff0c;在弹窗中输入“ipconfig”命令查看本地网络适配器的IP地址等配置信息&#xff1b; ②在Linux系统中&…

2025新春烟花代码(一)HTML5夜景放烟花绽放动画效果

标题预览效果 标题HTML代码 <!DOCTYPE html> <html lang"en"> <script>var _hmt _hmt || [];(function () {var hm document.createElement("script");hm.src "https://hm.baidu.com/hm.js?45f95f1bfde85c7777c3d1157e8c2d34&…

【Rust自学】10.6. 生命周期 Pt.2:生命周期的语法与例子

喜欢的话别忘了点赞、收藏加关注哦&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 10.6.1. 生命周期标注语法 生命周期的标注并不会改变引用的生命周期长度。如果某个函数它制定了泛型生命周期参数&#xff0c;那么它就可…

【C语言程序设计——选择结构程序设计】求一元二次方程的根(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 相关知识 sqrt() 函数 编程要求 测试说明 通关代码 测试结果 任务描述 本关任务&#xff1a;根据求根公式&#xff0c;计算并输出一元二次方程的两个实根&#xff0c;要求精确道小数点后2位。要求方程系数从键盘输入。如果输入的系数不满足求…

【C++数据结构——图】图的邻接矩阵和邻接表的存储(头歌实践教学平台习题)【合集】

目录&#x1f60b; 任务描述 相关知识 1. 带权有向图 2. 图的邻接矩阵 3. 图的邻接表 测试说明 通关代码 测试结果 任务描述 本关任务&#xff1a;编写一个程序实现图的邻接矩阵和邻接表的存储。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a; 带权有向图…

java 转义 反斜杠 Unexpected internal error near index 1

代码&#xff1a; String str"a\\c"; //出现异常&#xff0c;Unexpected internal error near index 1 //System.out.println(str.replaceAll("\\", "c"));//以下三种都正确 System.out.println(str.replace(\\, c)); System.out.println(str.r…

QML学习(七) 学习QML时,用好Qt设计器,快速了解各个组件的属性

在初步学习QML时&#xff0c;特别建议看看Qt设计器&#xff0c;先利用Qt Quick设计师的使用&#xff0c;快速的对Qt Quick的各个组件及其常用的属性&#xff0c;有个初步的了解和认识。如果初始学习一上来直接以代码形式开干&#xff0c;很容易一头雾水。而设计器以最直白的所见…

Flutter 鸿蒙化 flutter和鸿蒙next混和渲染

前言导读 这一个节课我们讲一下PlatformView的是使用 我们在实战中有可能出现了在鸿蒙next只加载一部分Flutter的情况 我们今天就讲一下这种情况具体实现要使用到我们的PlatformView 效果图 具体实现: 一、Native侧 使用 DevEco Studio工具打开 platform_view_example\oho…

js逆向实战(1)-- 某☁️音乐下载

下载某云音乐源文件.mp4格式 首先随便点进一首歌&#xff0c;如图所示获取该音乐id&#xff0c;然后点击播放键&#xff0c;打开F12进行查询XHR 由此可知&#xff0c;实际请求网址是 https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token「你的token」url需带…

学习随笔:word2vec在win11 vs2022下编译、测试运行

word2vec 官网word2vec的本质是在自然语言词条数据集与计算机浮点数据集之间建立双射关系。word2vec建立的数据集最厉害的一点是&#xff0c;将自然语言词条数据集内部的推理过程&#xff0c;映射到了计算机浮点数据集内部的数值运算。我个人感觉理解这个数据映射方式是理解AI大…