Java程序员必须掌握的数据结构:HashMap

HashMap底层原理实现是每个Java Boy必须掌握的基本技能,HashMap也是业务开发每天都需要遇到的好伙伴。如此基础且核心的底层数据结构,JDK也给其赋予了线程安全的功能类,我们来看看~

在这里插入图片描述

文章目录

    • 1. HashMap内部结构
      • 1.1 键值的添加流程
      • 1.2 红黑树
    • 2. 线程安全的Map
      • 2.1 线程不安全的HashMap
      • 2.2 线程安全的ConcurrentHashMap
      • 2.3 HashTable和ConcurrentHashMap区别
    • 未完待续。。。

1. HashMap内部结构

面试官:你说下HashMap的内部结构?

好的面试官。

HashMap内部存储数据的对象是一个实现Entry接口的Node数组,也称为哈希桶transient Node<K,V>[] table,后面我们称Node数组为Entry数组。Entry数组初始的大小是16

Node节点的内部属性key、value分别代表键和值,hash代表key的hash值,而next则是指向下一个链表节点的指针。

static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Node<K,V> next;
}

1.1 键值的添加流程

面试官:那一个键值是怎么存储到HashMap的?

首先会调用hash方法计算key的hash值,通过key的hashCode值与key的hashCode高16位进行异或运算,使hash值更加随机与均匀。

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

再通过该hash值与Entry数组的长度相与,得到要存储到的索引位置int index = (table.length - 1) & hash。如果该索引位置是空的,会把键值直接添加到表头,如果哈希冲突了则会用链表法形成一条链表。

数据添加后,会判断当前容量是否到达了threshold阈值,threshold等于负载因子loadFactor * table.length。负载因子默认是0.75,threshold第一次扩容时为0.75 * 16 = 12

如果到达阈值了则会对Entry数组进行扩容,扩容成为原来两倍容量的Entry数组。

1.2 红黑树

面试官:HashMap链表还会转换成什么?

当链表长度 >= 8时,会把链表转换为红黑树

是这样的,HashMap的链表元素如果数量过多,查询效率会越来越低,所以需要将链表转换为其他数据结构。而二叉搜索树这种数据结构是绝对的子树平衡,左节点比父节点小,右节点比父节点大,在极端情况会退化为链表结构

而红黑树放弃了绝对的子树平衡,转而追求的是一种大致平衡,在极端情况下的数据查询效率更优。

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {TreeNode<K,V> parent;  // red-black tree linksTreeNode<K,V> left;TreeNode<K,V> right;TreeNode<K,V> prev;    // needed to unlink next upon deletionboolean red;
}

2. 线程安全的Map

2.1 线程不安全的HashMap

面试官:HashMap为什么线程不安全?

一、在多线程环境下,可能会出现数据覆盖的问题。

例如前面提到如果索引位置为空则直接添加到表头,如下面源码所示。此时如果有两个线程同时进入if语句,线程A把数据插入到表头,接着线程B把他的数据覆盖到表头,这样就产生了数据覆盖的问题,线程A的数据相当于消失了。

if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);

二、另外在多线程环境下,还可能会出现数据不一致的问题。

在插入数据后,判断是否需要扩容是以下代码。

if (++size > threshold)resize();

若两个线程同时进入了++size代码块,对size的修改分为三个步骤:读取、计算、赋值。线程A、线程B同时读取了size是0,两者计算时size都为1,后面赋值时把size = 1赋值给了size两次。

但实际上期望的size应该是2,此时就出现了数据不一致的问题,Entry数组的容量会出现错误。

2.2 线程安全的ConcurrentHashMap

面试官:有线程安全的Map吗?

有的,JDK提供了线程安全的ConcurrentHashMap。

ConcurrentHashMap对于底层Entry数组、size容量都添加了可见性的修饰,保证了其他线程能实时监听到该值的最新修改

transient volatile Node<K,V>[] table;
private transient volatile int sizeCtl;

在添加键值的操作,对元素级别进行加锁。若该索引位置不存在元素,则使用乐观锁CAS操作来添加值,而CAS是原子操作,不怕多线程的干扰。

在这里插入图片描述

若该索引位置存在元素,则使用synchronized对该索引位置的头节点进行加锁操作,保证整条链表同一时刻只有一个线程在进行操作。

在这里插入图片描述

另外在JDK7版本中ConcurrentHashMap的实现和JDK8不同。

JDK7版本的数据结构是大数组Segment + 小数组HashEntry,其中小数组HashEntry的每个元素是一条链表,一个Segment是一个HashEntry数组。对每个Segment即每个分段,使用ReentrantLock进行加锁操作。

可以看到JDK8版本相比JDK版本的实现锁粒度更小,且JDK8版本的链表还可以升级为查询效率高的红黑树,所以JDK7版本的ConcurrentHashMap目前被JDK8版本的代替了。

2.3 HashTable和ConcurrentHashMap区别

面试官:HashTable和ConcurrentHashMap有什么区别吗?

HashTable也是线程安全的Map,不过它不仅对修改操作添加加锁操作,获取操作也进行了加锁。

public synchronized V put(K key, V value)
public synchronized V get(Object key)

而ConcurrentHashMap没有对get进行加锁处理,不适用于强一致性的场景。例如要求获取操作必须严格获取到最新的值,这种强一致性场景则更适合使用HashTable。

另外HashTable和HashMap、ConcurrentHashMap还有以下不同。

  1. HashTable继承了Dictionary,而HashMap、ConcurrentHashMap继承了AbstractMap
  2. HashTable初始容量为11,HashMap、ConcurrentHashMap是16
  3. HashTable扩容为原来的2n+1,HashMap、ConcurrentHashMap是扩容为原来的2n

🌱以【面试官面试】形式覆盖Java程序员所需掌握的Java核心知识、面试重点,本博客收录在我开源的《Java学习指南》中,会一直完善下去,希望收到大家的 ⭐ Star ⭐支持,这是我创作的最大动力: https://github.com/hdgaadd/JavaGetOffer

未完待续。。。

创作不易,不妨点赞、收藏、关注支持一下,各位的支持就是我创作的最大动力❤️

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蚂蚁 2025届暑期实习 多模态LLM 面经

文章目录 写在前面一面/技术面 2024/4/7 晚上19:00-20:00二面/技术面 2024/4/23 早上11:15-12:15三面/HR面 2024/4/25 11:00-11:50 写在前面 学校情况&#xff1a;211本中9硕&#xff0c;本硕都是计算机科班&#xff0c;但研究方向并不是NLP&#xff0c;而是图表示学习论文情况…

垃圾分类子项目-识别垃圾类型

思路: 阿里云图像识别 语音模块实现 主程序: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> // access() #include <error.h> // remove() #include "uartTool.h" #include "garbage.h&qu…

DFS和回溯专题:全排列 II

DFS和回溯专题&#xff1a;全排列 II 题目链接: 全排列 II 参考题解 代码随想录 题目描述 代码纯享版 class Solution {public List<List<Integer>> list_all new ArrayList();public List<Integer> list new ArrayList();public int[] res;public Lis…

【笔试训练】day11

1.游游的水果大礼包 思路&#xff1a; 枚举。假设最后的答案是x个a礼包&#xff0c;y个b礼包&#xff0c;得到一个式子&#xff1a;ansa*xb*y 我们可以枚举x的数量&#xff0c;这样就能变相的把y的求出来。呃这就是鸡兔同笼问题嘛 x最大的范围是多少呢&#xff1f;也就是a礼…

常见Linux操作系统SSH配置详解

SSH&#xff08;Secure Shell&#xff09;是一种网络协议&#xff0c;用于加密方式远程登录和操作计算机系统。Linux用户经常需要通过SSH来安全地管理系统。本文将详细介绍在不同Linux发行版&#xff08;CentOS、Ubuntu、RedHat、Debian、Fedora&#xff09;上配置SSH服务的步骤…

竞赛报名赛事管理系统技术分析

竞赛报名赛事管理系统是一个复杂的应用&#xff0c;涉及到用户管理、赛事信息管理、报名管理、成绩管理等多个方面。使用PHP框架来开发这样的系统&#xff0c;可以大大提高开发效率&#xff0c;保证代码的可维护性和可扩展性。以下是对基于PHP框架的竞赛报名赛事管理系统进行功…

vue3第二十四节(JSX用法)

vite 创建项目的情况下 安装 npm i vitejs/plugin-vue-jsx -D配置vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue import vueJsx from vitejs/plugin-vue-jsx // https://vitejs.dev/config/ export default defineConfig({plugins: […

网络编程——TCP的特性之自动重传/流量控制/拥塞控制,一篇说清楚

文章目录 1. ARQ自动重传协议1.1 停止等待ARQ1.2 连续ARQ1.3 总结 2. TCP的流量控制3. TCP的拥塞控制3.1 慢开始算法3.2 拥塞避免算法3.3 快重传算法3.4 快恢复算法 1. ARQ自动重传协议 自动重传请求&#xff08;Automatic Repeat-reQuest&#xff09;&#xff0c;通过使用确认…

前端获取文件后缀名

function getFileExtension(filename) {var parts filename.split(.);if (parts.length > 1) {return parts.pop();} else {return ;} }// 使用例子 var filename "example.png"; var extension getFileExtension(filename); console.log(extension); // 输出:…

免费的在线视频编辑工具,mp4转gif工具

在线视频编辑&#xff1a;https://online-video-cutter.com/change-video-speed&#xff0c;可以加速视频。 mp4转gif&#xff1a;MP4轉GIF轉換器。在线自由 — Convertio MP4 To GIF | Convert MP4 To GIF Images Online - XConvert

flex:1给了我工作机会

今天就跟大家讲讲flex&#xff1a;1是什么的缩写&#xff0c;怎么去理解这个样式。 首先要知道flex:1就是 flex-grow: 1; flex-shrink: 1; flex-basis: 0;的缩写&#xff0c;首页要理解它们的作用&#xff0c;先看dom解构 <div class"parent"><div class&q…

vivado Versal 串行 I/O 硬件调试流程、使用 Vivado Serial I/O Analyzer 来调试设计

Versal 串行 I/O 硬件调试流程 Versal ™ ACAP 无需再生成 IBERT IP &#xff0c; 因为使用系统内串行 I/O 调试所需的必要逻辑现已集成到 GTY 收发器架构内。使 用 GTY 收发器的任何设计均可用于串行 I/O 硬件调试。 Versal 串行 I/O 硬件调试流程具有 2 个不同阶…

lesson04:类和对象(下)

1. 再谈构造函数 2.static成员 3.友元 4.内部类 5.匿名对象 1. 再谈构造函数 1.1构造函数体内赋值 #define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class Date { public:Date(int year, int month, int day){_year year;_month mont…

Oceanbase体验之(二)Oceanbase集群的搭建(社区版4.2.2)

资源规划 3台observer CPU:4C及以上 内存&#xff1a;32G及以上 硬盘操作系统500G 存储盘1T及以上 虚拟机可以直接划分&#xff0c;物理机需要提前规划好资源 一、上传oceanbase安装包 登录ocp选择软件包管理 上传Oceanbase软件包&#xff08;软件包获取路径 官网免费下载社…

动态规划和递归法求解斐波那契数列

动态规划是把复杂问题分解为相对简单的子问题来求解,动态规划旨在解决具有重叠子问题和最优子结构特性的问题,它的核心思想是解决每个子问题仅能一次,并存储其解,以便需要时直接查找,从而避免重复计算 基本概念: 1.重叠子问题: 问题可以分解为多个子问题,且这些子问题有些会被多…

【量化】基于遗传规划的因子自动挖掘系统

最后&#xff1a;策略达到了23.6%的年化收益&#xff0c;夏普比率达到5.87&#xff0c;最大回撤为-4.3%&#xff0c;平局年换手率为27.45 倍&#xff0c;平均持股数量为543 支。 文末有回测结果。 目录 1.模型思想 1.1遗传规划算法介绍 ​1.2因子测试流程 2.代码与实现 2.1…

云原生Kubernetes: K8S 1.29版本 部署Nexus

目录 一、实验 1.环境 2.搭建NFS 3. K8S 1.29版本 部署Nexus 二、问题 1.volumeMode有哪几种模式 一、实验 1.环境 &#xff08;1&#xff09;主机 表1 主机 主机架构版本IP备注masterK8S master节点1.29.0192.168.204.8 node1K8S node节点1.29.0192.168.204.9node2K…

【yolo算法道路井盖检测】

yolo算法道路井盖检测 数据集和模型yolov8道路井盖-下水道井盖检测训练模型数据集pyqt界面yolov8道路井盖-下水道井盖检测训练模型数据集 算法原理 1. 数据集准备与增强 数据采集&#xff1a;使用行车记录仪或其他设备收集道路井盖的图像数据。数据标注&#xff1a;对收集到…

如何看待AIGC技术?【模板】

如何看待AIGC技术&#xff1f; 简介&#xff1a;探讨AIGC技术的发展现状和未来趋势。 提醒&#xff1a;在发布作品前&#xff0c;请把不需要的内容删掉。 方向一&#xff1a;技术应用 提示&#xff1a;分享AIGC技术在各个领域的应用情况&#xff0c;以及对未来社会的影响和可能…