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

news2024/10/1 21:30:55

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

但是,使用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.coloradmin.cn/o/2166306.html

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

相关文章

鸿蒙OpenHarmony【小型系统基础内核(互斥锁)】子系统开发

互斥锁 基本概念 互斥锁又称互斥型信号量&#xff0c;用于实现对共享资源的独占式处理。当有任务持有时&#xff0c;这个任务获得该互斥锁的所有权。当该任务释放它时&#xff0c;任务失去该互斥锁的所有权。当一个任务持有互斥锁时&#xff0c;其他任务将不能再持有该互斥锁…

Redis 主从复制的实现过程

Redis 主从复制的实现过程 1. 初始同步请求2. 快照生成与发送3. 从服务器载入数据4. 增量同步5. 持续同步与部分重同步 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; Redis 的主从复制是一个高效的数据同步机制&#xff0c;主要步骤为以下…

5分钟内不能重复发送验证码!

文章目录 引言校验5分钟内不能重复发送验证码生成验证内容保存验证码到缓存获取缓存验证内容验证短信验证码是否正确数据模型see also引言 防止被恶意攻击,使用需要限制用户获取验证码的频率,例如5分钟内不能重复发送验证码!获取验证码接口,也必须进行签名校验。 使用Red…

单片机STM32 外部中断线的使用笔记

一、STM32外部中断线问题小结 1.1 不同的端口同一PIN 在STM32中&#xff0c;不同的端口&#xff08;如PA、PB、PC等&#xff09;上的相同PIN号&#xff08;如PA1、PB1、PC1&#xff09;可以共用一个外部中断线&#xff08;EXTI_Line&#xff09;。这意味着&#xff0c;虽然这些…

[系列]相关的知识点关联

系列 独立&不相关不相关&正交协方差&互相关相关系数协方差&相关系数余弦系数&内积余弦系数&相关系数滤波&卷积卷积&互相关互相关&内积互相关&归一化互相关

Jenkins入门:从搭建到部署第一个Springboot项目(踩坑记录)

本文讲述在虚拟机环境下(模拟服务器)&#xff0c;使用docker方式搭建jenkins&#xff0c;并部署一个简单的Springboot项目。仅记录关键步骤和遇到的坑&#xff0c;后续再进行细节补充。 一、环境准备和基础工具安装 1. 环境 系统环境为本机vmware创建的Ubuntu24.04。 2. yum…

Unity 3D UGUI 系统一口气讲完!(^U^)ノ~YO

UGUI Canvas 画布 Canvas画布是摆放所有 UI 元素的区域&#xff0c;在场景中创建的所有控件都会自动变为 Canvas游戏对象的子对象。 若场景中没有画布&#xff0c;在创建控件时会自动创建画布。 不论是你主动创建还是被动创建&#xff0c;系统都会自动创建一个名为 EventSys…

使用Python解决数据分析中的相关性分析

目录 1.相关系数基础1.1 使用Pandas计算皮尔逊相关系数1.2 计算物品A与其他物品的相关系数1.3 用户间的相关系数1.4 获取相关系数矩阵 2. 相似度计算的Python实现2.1 欧式距离2.2 余弦相似度2.3 皮尔逊相关系数的手动实现 3. 总结 在数据分析中&#xff0c;相关系数是衡量两个变…

应用案例 | HK-MSR数据记录仪如何计算滑雪时膝盖上的应力?

计算滑雪时膝盖上的应力 阿尔卑斯山高山滑雪运动员在滑雪时对膝盖产生的压力有多大&#xff1f;Thea Waldleben&#xff0c;现任瑞士青年速降赛冠军&#xff0c;在她的 "Maturaarbeit"&#xff08;考试项目&#xff09;中回答了这个问题。通过使用HK-MSR数据记录仪&…

Python OpenCV精讲系列 - 边缘检测深入理解(十三)

&#x1f496;&#x1f496;⚡️⚡️专栏&#xff1a;Python OpenCV精讲⚡️⚡️&#x1f496;&#x1f496; 本专栏聚焦于Python结合OpenCV库进行计算机视觉开发的专业教程。通过系统化的课程设计&#xff0c;从基础概念入手&#xff0c;逐步深入到图像处理、特征检测、物体识…

地质工程专业职称申报条件详细解读

一、初级&#xff08;助理&#xff09;地质工程工程师评审条件&#xff1a; 1、理工类或者地质工程类专业毕业 2、专科毕业满3年或本科毕业满1年 3、研究生毕业&#xff0c;从事本专业技术工作&#xff0c;当年内考核认定 二、中级地质工程工程师评审条件&#xff1a; 1、理工…

解决远程连接AlpineLinux Mysql/MariaDB 无法连接的问题

&#x1f525;博客介绍&#xff1a; EvLast &#x1f3a5;系列专栏&#xff1a; << C项目>> <<数据结构与算法>> << 算法入门>> &#x1f3a5; 当前专栏:<< C项目>> 专题 : 解决开发中的日常Bug &#x1f44d;&#x1f44…

服务端的 Session 详解

0x01&#xff1a;Session 简介 Session 是在 Cookie 的基础上发展的&#xff0c;其主要功能和 Cookie 一样&#xff0c;都是为了解决 HTTP 无状态的痛点&#xff0c;和 Cookie 不同的是&#xff0c;它是将大部分数据存储在了服务端&#xff0c;而只给用户一个 SESSID&#xff…

django学习入门系列之第十点《A 案例: 员工管理系统21》

文章目录 16 Ajax(订单案例)16.9 编辑想要去数据库中获取数据时:对象/字典将具体内容的错误信息传入到前端&#xff08;Ajax&#xff09;将数据库数据传入前端&#xff08;Ajax&#xff09;清空新建订单的数据 16.10 编辑后保存 往期回顾 16 Ajax(订单案例) 16.9 编辑 点击编辑…

TCN预测 | MATLAB实现TCN时间卷积神经网络多输入单输出回归预测

TCN预测 | MATLAB实现TCN时间卷积神经网络多输入单输出回归预测 目录 TCN预测 | MATLAB实现TCN时间卷积神经网络多输入单输出回归预测预测效果基本介绍模型描述程序设计参考资料预测效果

Python第一篇:Python解释器

一&#xff1a;python解释器 python解释器是一款程序&#xff0c;用于解释、执行Python源代码。 一般python解释器都是c python使用c编写的&#xff0c;还有j python用java编写的。 二&#xff1a;python下载 三&#xff1a;使用示例 python进入控制台&#xff0c;python。 三…

【react案例】实现评论列表

1. 需求 展示评论列表实现删除功能 2.1 只有自己的评论才展示删除按钮 2.2 点击删除按钮&#xff0c;删除当前评论tab切换&#xff08;点击对应tab&#xff0c;对tab文案高亮处理&#xff09;评论高亮评论排序&#xff08;最新、最热&#xff09; 2. 实现思路 useState维护评…

基于vue框架的大同传统文化非物质文化宣传管理系统jzo9d(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,非遗类型,文化遗产 开题报告内容 基于Vue框架的大同传统文化非物质文化宣传管理系统开题报告 一、研究背景 在全球化加速的今天&#xff0c;传统文化的保护与传承面临着前所未有的挑战。大同&#xff0c;作为中国历史文化名城&a…

职场能力强的人都在做什么---今日头条

【职场里,能力强的人都在做哪些事... - 今日头条】https://m.toutiao.com/is/ikn6kt9q/ 知识雷达 2024-09-21 16:33 目录 职场里,能力强的人都在做哪些事呢? 1、复盘; 2、多角度思考;3、记录信息; 4、永远积极主动;5、主动获取信息差; 6、明确人和人的关系;7、…

STM32杂项

STM32杂项 1.启动过程2.中断3.GPIO4.Systick5.串口printf6.独立看门狗 记录单片机在工作中遇到的问题和特殊情况。 1.启动过程 M3/M4/M7内核复位后&#xff0c;做的第一件事&#xff1a; 1.从地址0x0000 0000处取出堆栈指针MSP的初始值&#xff0c;该值就是栈顶地址。 2.从地址…