优化java中 HashMap 的容量](capacity值)

我们很多人都知道,分配比我们所需更多的内存可能会对应用程序的性能产生负面影响。因此,使用带有容量的构造函数创建列表可能会产生很大的不同。

但是,使用Maps时,这个优化步骤可能不是那么简单。在本文中,我们将学习如何识别HashMap 中过度分配和分配不足的问题 ,更重要的是,如何解决它。

例子

让我们考虑这个代码片段,其中我们创建一个HashMap并用几个条目填充它:

public static void main(String[] args) {for (int i = 0; i < 1_000_000_000; i++) {final HashMap<String, Integer> workDays = new HashMap<>();workDays.put(new String("Monday"), 1);workDays.put(new String("Tuesday"), 2);workDays.put(new String("Wednesday"), 3);workDays.put(new String("Thursday"), 4);workDays.put(new String("Friday"), 5);MAP_ACCUMULATOR.add(workDays);}}

这里的问题是HashMap的默认初始容量为 16,而对于上面的情况,我们可以毫无问题地使用 8。幸运的是,如果我们使用HeapHero分析堆转储,这个问题就会变得非常明显:

使用 HeapHero 分析低效集合
图 1:使用 HeapHero 分析低效集合

低效集合部分显示占用空间过多的集合。在这里,我们可以看到我们的HashMap包含的元素比集合可以容纳的少得多。

但是,让我们考虑一个更微妙的例子:

public static void main(String[] args) {for (int i = 0; i < 1_000_000_000; i++) {final HashMap<String, Integer> planets = new HashMap<>(8);planets.put(new String("Mercury"), 4879);planets.put(new String("Venus"), 12104);planets.put(new String("Earth"), 12742);planets.put(new String("Mars"), 6779);planets.put(new String("Jupiter"), 139820);planets.put(new String("Saturn"), 116460);planets.put(new String("Uranus"), 50724);planets.put(new String("Neptune"), 49244);MAP_ACCUMULATOR.add(planets);}}

我们有八颗行星和一张容量设置为八的地图。你们中的一些人已经注意到了这个问题。但是,让我们分析一下这个小应用程序的堆转储。让我们也使用 HeapHero 分析器来分析:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
图 2:在某些情况下,HeapHero 不会直接检测到问题

低效的信息收集并没有显示出任何问题。 我们必须经过几个步骤和计算才能确定问题所在。为此,我们需要比较我们的地图占用的保留堆和保留节点的内部数组占用的保留堆。差异在于显示我们有多少个空元素的开销:

HeapHero 报告中的对象直方图
图 3:对象直方图

我们可以看到,HashMaps的整体保留堆几乎为 2GB。但是,节点的总保留堆只有 1.62 GB。**我们有近 400MB 的开销或 5% 的未使用空间。 **

根据堆转储,我们可以看到Map的实际大小高于我们预期的,并且浪费了一些空间。

然而,对于这个问题我们无能为力。map 的容量应该始终是 2 的幂。 此外,我们还应该考虑负载因子:本文将进一步解释。这就是为什么即使创建具有正确容量的 Map ,我们的空间开销也是一样的。

同时,在创建分配不足的地图时,我们还遇到了另一个问题。让我们使用JMH分析代码。我们将进行简单的测试,这些测试将在循环中创建地图:

@Measurement(iterations = 1, time = 2, timeUnit = TimeUnit.MINUTES)@Warmup(iterations = 1, time = 10)@Fork(1)public class MapCapacityOverhead {@Benchmark@BenchmarkMode(Mode.Throughput)public void mapWithUnderestimatedCapacity(Blackhole blackhole) {final HashMap<String, Integer> map = new HashMap<>(8);map.put(new String("Mercury"),4879);map.put(new String("Venus"),12104);map.put(new String("Earth"),12742);map.put(new String("Mars"),6779);map.put(new String("Jupiter"),139820);map.put(new String("Saturn"),116460);map.put(new String("Uranus"),50724);map.put(new String("Neptune"),49244);blackhole.consume(map);}@Benchmark@BenchmarkMode(Mode.Throughput)public void mapWithCorrectCapacity(Blackhole blackhole) {final HashMap<String, Integer> map = HashMap.newHashMap(8);map.put(new String("Mercury"),4879);map.put(new String("Venus"),12104);map.put(new String("Earth"),12742);map.put(new String("Mars"),6779);map.put(new String("Jupiter"),139820);map.put(new String("Saturn"),116460);map.put(new String("Uranus"),50724);map.put(new String("Neptune"),49244);blackhole.consume(map);}}

从结果中我们可以看出,它们的吞吐量差别很大:

基准模式计数分数错误单位
具有正确容量的地图节肢动物7256575.859操作/秒
容量低估的地图节肢动物5581449.247操作/秒

使用低估的初始容量会导致代码比我们在初始化Map时分配正确数量的存储桶时慢 25% 左右。

容量与映射

要理解的主要内容是ListMap 的容量之间的区别 列表的容量很简单:我们计划存储在列表中的元素数量*。 *

然而,在 HashMap 中, 我们需要考虑另一个重要参数:负载因子。 默认情况下, HashMap使用设置为 0.75 的负载因子,这意味着Map**永远不会达到其满容量,并且当其达到 75% 满容量时将增加其大小。 **

我们在前面的例子中看到了这一点,问题是我们应该分配多少容量来存储特定数量的元素。正确执行此操作将节省我们的空间和时间 - 因为我们不会浪费处理能力来重新散列映射中的条目。

计算容量

现在,当我们知道容量不等于映射数时,我们假设它始终是正确的。 但是,从技术上讲,我们可以将负载因子设置为 100%,这会产生另一个问题:哈希冲突。

有多种方法可以计算给定映射数量的正确容量。让我们回顾一下其中的一些。

1.佐藤直人的配方

这个公式相对容易使用和理解:

int capacity =  (int) (number of mappings/load factor) + 1;

但是,它可能会为某些值分配比所需更多的内存。如果我们的负载因子为 0.75,那么此方法将为可被三整除的映射数分配额外的空间:

int capacity = (int) ( 6 / 0.75) + 1 = 9;

从技术上讲,最终的容量是正确的,但八个存储桶足以容纳六个元素,无需调整大小。 如果我们使用此公式创建需要容纳六个元素的 Map ,最终将预分配十六个元素。请记住, HashMap中的内部表的大小应始终为 2 的幂。

2. Google Guava 的公式

此公式与上一个公式类似,但整个计算使用浮点数:

int capacity = (int) ((float) numMappings / 0.75f + 1.0f);

但是,如果前一个公式存在高估容量的问题,那么这个公式则存在相反的问题。由于浮点 运算和舍入, HashMap 的容量可能会更小,从而迫使重新哈希。

3.十进制算术

这两个公式不存在舍入和不完美浮点计算的问题。第一个公式直接使用整数**:

int capacity = (numMappings * 4 + 2) / 3;

第二个公式使用 long

int capacity =  (int) ((numMappings * 4L + 2L) / 3L);

不幸的是,两者都存在整数溢出问题,这可能导致负数或远离最佳值的数字。

4. 上限公式

计算容量的另一个简单公式是使用 Math.ceil(float)

int capacity = (int) Math.ceil(numMappings / 0.75f);

由于精度操作,结果可能会导致对特定大数的低估。

5. Java 19 API

Java 19 引入了一个新的静态工厂, HashMap.newHashMap(int) 获取映射数并透明地计算容量。 这种新方法是一种直接创建具有所需容量的HashMap 的方法,不会高估或低估。

桶数

虽然我们可以在创建 HashMap 时传递所需的容量 但这并不意味着该映射将包含指定数量的 bucket。出于性能原因,bucket 的数量是最接近的 2 的幂的较大或相等的值。

例如,如果我们将容量设为 8:

final int initialCapacity = 8;final HashMap<String, String> map = new HashMap<>(initialCapacity);map.put("Hello", "World");final int actualCapacity = getCapacity(map);System.out.println("The initial capacity is %d and the actual one is %d".formatted(initialCapacity, actualCapacity));

实际容量为 8:

The initial capacity is 8, and the actual one is 8

同时,如果我们再多要求一点的话:

final int initialCapacity = 9;final HashMap<String, String> map = new HashMap<>(initialCapacity);map.put("Hello", "World");final int actualCapacity = getCapacity(map);System.out.println("The initial capacity is %d, and the actual one is %d".formatted(initialCapacity, actualCapacity));

容量将是最接近的 2 的幂的较大数字,即十六:

The initial capacity is 9, and the actual one is 16

请注意,方法getCapacity是一个使用反射获取容量的自定义方法。

结论

为了分配正确数量的 bucket,最好且最易读的方法是使用 Java 19 API 创建 HashMap 但是,有时由于限制或历史原因,无法升级 Java 版本。

下一个最佳解决方案是使用 Naoto 公式,这是上面介绍的唯一无错误的方法。 它不是完美的最佳方法,但它不会强制重新散列。另一个好处是它很容易记住和理解。

总体而言,每个应用程序都有其特定的问题和可能的优化。 解决这些问题的最佳方法是使用诊断工具(例如yCrash)来检查其内存使用情况和低效集合,无论是在专用部分还是通过分析保留堆

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Django 基础之启动命令和启动配置修改

Django启动 django启动一般可以通过ide或者命令启动 ide启动&#xff1a; 启动命令&#xff1a; python manage.py runserver该命令后续可以增加参数&#xff0c;如&#xff1a; python manage.py runserver 8081 python manage.py runserver 127.0.0.1:8082 注意&#xff1…

PDF转换器哪个好?这5款PDF工具值得推荐

PDF转换器哪个好&#xff1f;选择一款优质的PDF转换器&#xff0c;能够极大地提升我们的工作效率与灵活性。它不仅能轻松实现PDF文件与Word、Excel、PPT等多种格式间的互转&#xff0c;还支持图片、TXT等多种格式的转换&#xff0c;满足多样化的办公与学习需求。此外&#xff0…

南卡首款耳夹开放式耳机,舒适与音质双双达行业峰值,再次“颠覆”行业!

近日&#xff0c;南卡Ultra夹耳式蓝牙耳机的正式上市&#xff0c;再次在蓝牙耳机圈内掀起波澜。这款耳机以其超舒适的夹耳式设计和卓越音质&#xff0c;为用户带来了全新的音乐体验&#xff0c;有望重新定义夹耳式耳机的市场标准。 南卡品牌背后有着强大的研发实力和丰富的行业…

一文读懂Service以及实践攻略

一文读懂Service以及实践攻略 目录 1 一文读懂 Kubernetes Service 以及实践攻略 1.1 1. 什么是 Service&#xff1f; 1.1.1 为什么需要 Service&#xff1f; 1.2 2. Service 的工作原理 1.2.1 核心概念1.2.2 流量转发过程 1.3 3. Service 的几种类型及应用场景 2 实践&#…

网络与信息安全工程师(工信部教育与考试中心)

在当今数字化时代&#xff0c;大量的敏感信息与业务流程在网络上传输和处理&#xff0c;使得网络与信息安全成为保障企业运营、政务管理以及金融交易等关键领域不可忽视的一环。 因此&#xff0c;对网络安全专家的需求日益增长。 例如&#xff0c;金融机构、大型电信运营商以…

云专线和虚拟专线是什么?两者有什么区别?

云专线和虚拟专线是两种用于企业网络连接的技术方案&#xff0c;它们各自有不同的特点和适用场景。下面将分别介绍这两种技术&#xff0c;并指出它们之间的主要区别。 云专线 云专线是一种物理的或逻辑的专用线路&#xff0c;它直接连接企业的本地数据中心或分支机构与云端服务…

为什么美联储降息和我国刺激措施可能提振铜价

美联储降低利率通常对铜价产生积极影响。这主要是由于利率与美元汇率之间的关系。当美联储降息时&#xff0c;往往会使美元对其他货币贬值。 由于全球市场上的铜价是以美元计价的&#xff0c;美元走弱会使用其他货币购买的金属价格更便宜。这可能刺激来自国际买家的需求&#x…

RTE 大会报名丨AI 时代新基建:云边端架构和 AI Infra ,RTE2024 技术专场第二弹!

所有 AI Infra 都在探寻规格和性能的最佳平衡&#xff0c;如何构建高可用的云边端协同架构&#xff1f; 语音 AI 实现 human-like 的最后一步是什么&#xff1f; AI 视频的爆炸增长&#xff0c;给新一代编解码技术提出了什么新挑战&#xff1f; 当大模型进化到实时多模态&am…

【深度学习】(7)--神经网络之保存最优模型

文章目录 保存最优模型一、两种保存方法1. 保存模型参数2. 保存完整模型 二、迭代模型 总结 保存最优模型 我们在迭代模型训练时&#xff0c;随着次数初始的增多&#xff0c;模型的准确率会逐渐的上升&#xff0c;但是同时也随着迭代次数越来越多&#xff0c;由于模型会开始学…

亲测好用,吐血整理 ChatGPT 3.5/4.0新手使用手册~

都知道ChatGPT很强大&#xff0c;聊聊天、写论文、搞翻译、写代码、写文案、审合同等等&#xff0c;无所不能~ 那么到底怎么使用呢&#xff1f;其实很简单了&#xff0c;国内AI产品发展也很快&#xff0c;很多都很好用了~ 我一直在用&#xff0c;建议收藏下来~ 有最先进、最…

PHP程序如何实现限制一台电脑登录?

PHP程序如何实现限制一台电脑登录&#xff1f; 可以使用以下几种方法&#xff1a; 1. IP地址限制&#xff1a;在PHP中&#xff0c;可以通过获取客户端的IP地址&#xff0c;然后与允许登录的IP地址列表进行比对。如果客户端的IP地址不在列表中&#xff0c;就禁止登录。 “php $…

U盘未格式化之谜:数据丢失与恢复全攻略

U盘未格式化问题描述 在日常的数字生活中&#xff0c;U盘作为便携的数据存储设备&#xff0c;扮演着不可或缺的角色。然而&#xff0c;不少用户都曾遭遇过这样的困境&#xff1a;当尝试访问未进行格式化操作的U盘时&#xff0c;却发现原本存储的文件竟然不翼而飞&#xff0c;U…

【机器学习】音乐生成——AI如何创作个性化音乐与配乐

我的主页&#xff1a;2的n次方_ 音乐是人类文化的重要组成部分&#xff0c;它具有极强的情感表达和艺术价值。近年来&#xff0c;随着人工智能技术的飞速发展&#xff0c;AI已经能够自动生成音乐&#xff0c;甚至根据用户需求创作个性化配乐。AI生成音乐的应用场景广泛&…

35岁java转大模型笔记,大模型智能体(LLM Agent)学习笔记

\1. 什么是大模型&#xff1f; 大模型对应的英文是Large Language Model&#xff08;LLM&#xff09;&#xff0c;即大语言模型&#xff0c;简称大模型。技术层面讲&#xff0c;大模型是一种基于深度学习技术的机器学习模型。 为什么叫大模型呢&#xff1f;它是相对于小模型而…

代码随想录算法训练营第十四天|递归 226.翻转二叉树 101. 对称二叉树 104.二叉树的最大深度 111.二叉树的最小深度

226.翻转二叉树 翻转一棵二叉树。 思路&#xff1a; 在这里需要注意的是&#xff0c;在递归的时候唯独中序遍历是不可用的&#xff0c;这是因为先对左子树进行了反转&#xff0c;又对自身进行了反转&#xff0c;对自身反转后原本的左子树变成了右子树&#xff0c;如果此时又轮…

流媒体服务软件-LiveNVR channeltree 未授权访问

0x01 产品描述&#xff1a; LiveNVR 能够通过简单的网络摄像机通道配置&#xff0c;将传统监控行业里面的高清网络摄像机 IPCamera、NVR 等具有 RTSP/Onvif 协议输出的设备接入到 LiveNVR&#xff0c;LiveNVR 能够将这些设备源的音/视频数据进行采集、转换、输出&#xff0c;进…

MySQL 5.8 Performance Schema 配置详解

MySQL 5.8 Performance Schema 配置详解 MySQL 的 Performance Schema 是一个用于监控和优化数据库性能的子系统&#xff0c;专门用来收集 MySQL 服务器的运行情况和性能指标。它的核心原理是通过“生产者”和“消费者”的概念来采集和存储数据库中的事件信息&#xff0c;帮助…

UNI-SOP应用场景(2)- 业务平台集成

前面介绍了项目前期我们前端可以不需要业务平台参与就可以开始开发&#xff0c;这一章我们介绍新业务平台怎么集成到UNI-SOP云统一认证中心。 新建项目引入uni-client在云认证统一管理端新增业务平台在业务平台项目配置在认证中心创建的平台信息接口开发和权限校验 新建项目 打…

怎么辨别ip地址类别:方法与实践

在当今的互联网世界中&#xff0c;IP地址作为网络设备的唯一标识&#xff0c;扮演着至关重要的角色。无论是进行网络管理、安全监控还是日常的网络通信&#xff0c;对IP地址的理解都是必不可少的。而IP地址的类别&#xff0c;更是决定了其使用范围和应用场景。本文旨在为读者提…

H5响应式的文化传媒娱乐公司HTML网站模板源码

源码名称&#xff1a;响应式的文化传媒娱乐公司HTML网站模板源码 源码介绍&#xff1a;一款自适应H5文化传媒娱乐公司官网源码&#xff0c;源码带有6个H5页面&#xff0c;可用于文化传媒和娱乐公司官网。 需求环境&#xff1a;H5 下载地址&#xff1a; https://www.51888w.c…