【Java集合类篇】HashMap的数据结构是怎样的?

在这里插入图片描述

HashMap的数据结构是怎样的?

  • ✔️HashMap的数据结构
    • ✔️ 数组
    • ✔️ 链表


✔️HashMap的数据结构


在Java中,保存数据有两种比较简单的数据结构: 数组和链表(或红黑树)。


HashMapJava 中常用的数据结构,它实现了 Map 接口。HashMap通过键值对的形式存储数据,其中键是唯一的,而值可以是任何对象。HashMap底层使用数组和链表(或红黑树)来实现。


常用的哈希函数的冲突解决办法中有一种方法叫做链地址法,其实就是将数组和链表组合在一起,发挥了两者的优势,我们可以将其理解为链表的数组。在JDK 1.8之前,HashMap就是通过这种结构来存储数据的。


在这里插入图片描述


我们可以从上图看到,左边很明显是个数组,数组的每个成员是一个链表。该数据结构所容纳的所有元素均包含一个指针,用于元素间的链接。我们根据元素的自身特征把元素分配到不同的链表中去,反过来我们也正是通过这些特征找到正确的链表,再从链表中找出正确的元素。其中,根据元素特征计算元素数组下标的方法就是哈希算法,即本文的主角 hash() 函数 (当然,还包括indexOf()函数)。


✔️ 数组


数组:HashMap使用一个数组来存储键值对。数组的每个元素都是一个桶(bucket),桶中存储着一个链表(LinkedList)或红黑树(TreeMap)。桶的数量可以根据需要动态调整。数组的索引方式采用哈希算法,通过将键的哈希值对数组长度取模来得到对应的桶。


数组的特点是:寻址容易,插入和删除困难。


看一个如何使用数组实现HashMap的代码片段:


public class MyHashMap<K, V> { // 默认初始容量  private static final int DEFAULT_INITIAL_CAPACITY = 16;  // 默认加载因子  private static final float DEFAULT_LOAD_FACTOR = 0.75f;  // 存储键值对的数组 private Entry<K, V>[] table;  // 当前容量 private int capacity; // 实际存储的键值对数量   private int size;  public MyHashMap() {  this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);  }  public MyHashMap(int capacity) {  this(capacity, DEFAULT_LOAD_FACTOR);  }  public MyHashMap(int capacity, float loadFactor) {  this.capacity = capacity;  table = new Entry[capacity];  size = 0;  }  public V put(K key, V value) {  int hash = hash(key);  int index = indexFor(hash, table.length);  Entry<K, V> oldValue = table[index];  if (oldValue == null) {  table[index] = new Entry<>(key, value, null);  size++;  if (size > capacity * loadFactor) {  rehash();  }  } else {  Entry<K, V> newEntry = new Entry<>(key, value, oldValue);  table[index] = newEntry;  }  return oldValue == null ? null : oldValue.value;  }  public V get(K key) {  int hash = hash(key);  int index = indexFor(hash, table.length);  Entry<K, V> entry = table[index];  if (entry != null && Objects.equals(entry.key, key)) {  return entry.value;  } else {  return null;  }  }  public int size() {  return size;  }  private int hash(Object key) {  return Objects.hashCode(key);  }  private int indexFor(int hash, int length) {  return hash % length;  }  private void rehash() {  Entry<K, V>[] oldTable = table;  int oldCapacity = oldTable.length;  int newCapacity = oldCapacity * 2;  Entry<K, V>[] newTable = new Entry[newCapacity];  for (Entry<K, V> oldEntry : oldTable) {  while (oldEntry != null) {  Entry<K, V> next = oldEntry.next;  int hash = hash(oldEntry.key);  int index = indexFor(hash, newCapacity);  oldEntry.next = newTable[index];  newTable[index] = oldEntry;  oldEntry = next;  }  }  table = newTable;  capacity = newCapacity;  }  
}

✔️ 链表


链表:当多个键的哈希值映射到同一个桶时,它们会形成一个链表。链表中的每个节点包含一个键值对和指向下一个节点的指针。链表的作用是在插入、删除和查找操作时解决哈希冲突。


链表的特点是: 寻址困难,插入和删除容易


看一个如何使用链表实现HashMap的代码片段,是一个简单的HashMap实现,使用链表来处理哈希冲突:


public class MyHashMap<K, V> {  private static class Entry<K, V> {  K key;  V value;  Entry<K, V> next;  Entry(K key, V value, Entry<K, V> next) {  this.key = key;  this.value = value;  this.next = next;  }  }  private Entry<K, V>[] table;  private int capacity;  private int size;  private float loadFactor;  public MyHashMap(int capacity, float loadFactor) {  if (capacity < 0) throw new IllegalArgumentException("Capacity must be non-negative");  if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Load factor must be positive");  this.capacity = capacity;  this.loadFactor = loadFactor;  table = new Entry[capacity];  size = 0;  }  public V put(K key, V value) {  if (key == null) return null; // HashMaps don't allow null keys  // If size exceeds load factor * capacity, rehash  if (size >= capacity * loadFactor) {  rehash();  }  int hash = hash(key);  int index = indexFor(hash, capacity);  Entry<K, V> entry = table[index];  if (entry == null) {  // No collision, create new entry  table[index] = new Entry<>(key, value, null);  size++;  } else {  // Collision occurred, handle it using chaining  while (entry != null && !entry.key.equals(key)) {  if (entry.next == null) {  // End of chain, insert new entry  entry.next = new Entry<>(key, value, null);  size++;  break;  }  entry = entry.next;  }  // If key already exists, update value  if (entry != null && entry.key.equals(key)) {  V oldValue = entry.value;  entry.value = value;  return oldValue;  }  }  return null; // If key was new or not found  }  public V get(K key) {  if (key == null) return null; // HashMaps don't allow null keys  int hash = hash(key);  int index = indexFor(hash, capacity);  Entry<K, V> entry = table[index];  while (entry != null && !entry.key.equals(key)) {  entry = entry.next;  }  return entry == null ? null : entry.value;  }  private void rehash() {  capacity *= 2;  Entry<K, V>[] oldTable = table;  table = new Entry[capacity];  size = 0;  for (Entry<K, V> entry : oldTable) {  while (entry != null) {  Entry<K, V> next = entry.next;  int hash = hash(entry.key);  int index = indexFor(hash, capacity);  entry.next = table[index];  table[index] = entry;  size++;  entry = next;  }  }  }  private int hash(K key) {  return Math.abs(key.hashCode()) % capacity;  }  private int indexFor(int hash, int length) {  return hash % length;  }  public static void main(String[] args) {  MyHashMap<String, Integer> map = new MyHashMap<>(16, 0.75f);  map.put("one", 1);  map.put("two", 2);  map.put("three", 3);  System.out.println(map.get("one"));    // Should print 1  System.out.println(map.get("two"));    // Should print 2  System.out.println(map.get("three"));  //Should print 3

在JDK 1.8中为了解决因hash冲突导致某个链表长度过长,影响 put get 的效率,引入了红黑树。


关于红黑树,下一篇会作为单独的博文进行更新。

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

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

相关文章

STM32 CubeMX产生的程序架构

使用STM32CubeMX产生启动相关代码&#xff0c;配置各种外设。在后续程序开发过程中&#xff0c;有可能使用STM32CubeMX逐步产生使用的代码&#xff0c;为了将其产生的代码和我们程序隔离&#xff0c;一种可行的程序架构如下&#xff1a; 在此架构中&#xff0c;STM32CubeMX产生…

GraphQL和REST API的区别

我的新书《Android App开发入门与实战》已于2020年8月由人民邮电出版社出版&#xff0c;欢迎购买。点击进入详情 GraphQL&#xff08;Graph Query Language&#xff09;和REST&#xff08;Representational State Transfer&#xff09;是两种用于构建和设计API的不同方法。以下…

电脑屏幕一直闪烁怎么解决?三大妙招还你清晰视野

电脑屏幕闪烁一直是困扰用户的一个问题&#xff0c;它会影响到用户的使用体验&#xff0c;甚至可能导致眼睛疲劳和头痛。可是电脑屏幕一直闪烁怎么解决呢&#xff1f;在本文中&#xff0c;我们将介绍三个解决电脑屏幕闪烁的方法&#xff0c;从简单的软件调整到硬件检测&#xf…

亚马逊图片上传后变模糊怎么办?亚马逊图片优化指南—站斧浏览器

亚马逊图片上传后变模糊怎么办&#xff1f; 使用高分辨率图片&#xff1a;亚马逊建议卖家使用至少1000 x 1000像素的高分辨率图片。如果您上传的图片分辨率较低&#xff0c;亚马逊系统可能会将其自动调整为较小的尺寸&#xff0c;导致图片模糊。因此&#xff0c;确保您使用高质…

基于Java SSM框架实现宠物管理系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架实现宠物管理系统演示 摘要 随着我国经济的快速发展&#xff0c;人民生活水平的不断提高&#xff0c;宠物逐渐成为许多宠物爱好者的一种生活方式。 宠物的品种也越来越多样化&#xff0c;宠物不仅能给生活带来乐趣还可以成为空巢老人&#xff0c;独生子女很…

MongoDB批量写入操作

一、概述 MongoDB为客户端提供了批量执行写入操作的能力。批量写入操作影响单个集合。MongoDB允许应用程序确定批量写入操作所需的可接受确认级别。 db.collection.bulkWrite&#xff08;&#xff09;方法提供了执行批量插入、更新和删除操作的能力。 MongoDB还支持通过db.col…

补码的乘法-布斯乘法

前言 本篇文章讲解如何通过逻辑门的形式来实现补码的乘法操作 布斯乘法 A.D.Booth提出了一种补码相乘算法,可以将符号位与数值位合在一起参与运算&#xff0c;直接得出用补码表示的乘积&#xff0c;且正数和负数同等对待。这种算法被称之为Booth (布斯)乘法 下面有两个变量值…

【量化金融】《证券投资学》吴晓求(第四版)(更新中)

这里写目录标题 第一篇 基本知识第1章 证券投资工具名词解释简答题 第2章 证券市场名词解释简答题 第二篇 基本分析第三篇 技术分析第四篇 组合管理第五篇 量化分析与交易策略 第一篇 基本知识 第1章 证券投资工具 名词解释 风险&#xff08;risk&#xff09; 未来结果的不…

【hcie-cloud】【16】业务上云迁移、Rainbow详述

文章目录 前言华为业务迁移解决方案概述业务上云背景概述业务迁移场景需求及挑战业务迁移的价值华为业务迁移解决方案 - 全景图华为业务迁移解决方案的优势 Rainbow迁移工具介绍Rainbow迁移原理介绍Rainbow迁移工具简介Rainbow迁移工具定位Rainbow迁移视图Rainbow迁移原理 - Wi…

JumpServer3.0版本-账号管理

账号列表 我这里已经创建好了所以有很多,可以点击资产树列表分类查看 点击创建按钮,添加账号 资产:如果多个设备的账号密码一致可以在资产同事选中 名称:方便辨识即可 用户名:登录设备的账户名 密码:按你登录需求自行选择 添加按钮旁边还有个“模版添加” 此功能便…

yolov8实战第五天——yolov8+ffmpg实时视频流检测并进行实时推流——(推流,保姆教学)

yolov8实战第一天——yolov8部署并训练自己的数据集&#xff08;保姆式教程&#xff09;_yolov8训练自己的数据集-CSDN博客 yolov8实战第三天——yolov8TensorRT部署&#xff08;python推理&#xff09;&#xff08;保姆教学&#xff09;-CSDN博客 今天&#xff0c;我们继续y…

Java TBA访问NetSuite Restlet时的403错误

本周有同学问为啥Java访问NetSuite Restlet时&#xff0c;按照知识会之前的文章分享&#xff0c;会一直报403 INVALID_LOGIN_ATTEMPT错误。 https://nk-community.blog.csdn.net/article/details/131399801https://nk-community.blog.csdn.net/article/details/131399801原因是…

线程同步之:QReadWriteLock

1、 使用互斥量QMutex时候存在一个问题 每次只能有一个线程获得互斥量的权限。如果在一个程序中有多个线程“读”取某个变量&#xff0c;使用互斥量时也必须排队&#xff01; 然而&#xff0c;实际情况是允许&#xff1a;让多个线程同时“读”&#xff01; 这样互斥量 就会降…

面试官:说说flexbox(弹性盒flex布局),以及适用场景?

面试官&#xff1a;说说flexbox&#xff08;弹性盒布局模型&#xff09;,以及适用场景&#xff1f; 一、是什么 Flexible Box 简称 flex&#xff0c;意为”弹性布局”&#xff0c;可以简便、完整、响应式地实现各种页面布局 采用Flex布局的元素&#xff0c;称为flex容器conta…

面试官:线程池的7种创建方式,你都清楚吗?

文章目录 前言1. 固定数量的线程池a. 线程池返回结果b. ⾃定义线程池名称或优先级 2. 带缓存的线程池3. 执⾏定时任务a. 延迟执行(一次)b. 固定频率执行c. scheduleAtFixedRate VS scheduleWithFixedDelay 4. 定时任务单线程5. 单线程线程池6. 根据当前CPU⽣成线程池 前言 线程…

CRM管理系统八大特性,如何帮助企业提升业务效能?

CRM管理系统的使用率逐年上升&#xff0c;这离不开企业对客户的依赖——管理好客户关系是企业发展你的重要因素&#xff0c;由此可见CRM对于任何成长中的企业都是必不可少的。以前我们写过很多关于CRM功能的文章&#xff0c;这篇文章换个思路&#xff0c;想一想CRM管理系统主要…

Pytorch从零开始实战15

Pytorch从零开始实战——ResNeXt-50算法实战 本系列来源于365天深度学习训练营 原作者K同学 文章目录 Pytorch从零开始实战——ResNeXt-50算法实战环境准备数据集模型选择开始训练可视化总结 环境准备 本文基于Jupyter notebook&#xff0c;使用Python3.8&#xff0c;Pytor…

Unity中Shader序列图动画(UV流动的通用起始点)

文章目录 前言一、一般序列帧动画是按照序列图如下顺序读取的二、在Shader找到UV流动的起始点1、先实现纹理采样2、得到 uv 走格的单位格子大小3、定位到左上角为起始单位格 三、使UV流动的起始点通用化1、在属性面板接收 行 和 列的属性2、看图片可以总结出第一个单元格的公式…

Kotlin/JS工程构建及编译运行到浏览器

概述 Kotlin/JS 提供了转换 Kotlin 代码、Kotlin 标准库的能力&#xff0c;并且兼容 JavaScript 的任何依赖项。Kotlin/JS 的当前实现以 ES5 为目标。 使用 Kotlin/JS 的推荐方法是通过 kotlin.multiplatform Gradle 插件。它提供了一种集中且便捷的方式来设置与控制以 JavaS…

华为DriveONE电机控制器拆解实拍

如果说之前的问界M5、M7&#xff0c;华为让我们看到其在智能化上确实拥有遥遥领先的能力&#xff0c;那么在智界S7上&#xff0c;则让我们看到华为在动力、底盘这些硬件执行层面&#xff0c;竟然也有不输给很多车企的实力。1、华为电驱&#xff0c;全球第一&#xff1f;在智界S…