C++之深入解析STL unordered_map的底层实现原理

news2024/9/24 17:21:31
  • C++ STL 标准库中,不仅是 unordered_map 容器,所有无序容器的底层实现都采用的是哈希表存储结构。更准确地说,是用“链地址法”(又称“开链法”)解决数据存储位置发生冲突的哈希表,整个存储结构如下所示(C++ STL 无序容器存储状态,其中 Pi 表示存储的各个键值对):

在这里插入图片描述

  • 可以看到,当使用无序容器存储键值对时,会先申请一整块连续的存储空间,但此空间并不用来直接存储键值对,而是存储各个链表的头指针,各键值对真正的存储位置是各个链表的节点(注意,STL 标准库通常选用 vector 容器存储各个链表的头指针)。
  • 不仅如此,在 C++ STL 标准库中,将上图中的各个链表称为桶(bucket),每个桶都有自己的编号(从 0 开始)。当有新键值对存储到无序容器中时,整个存储过程分为如下几步:
    • 将该键值对中键的值带入设计好的哈希函数,会得到一个哈希值(一个整数,用 H 表示);
    • 将 H 和无序容器拥有桶的数量 n 做整除运算(即 H % n),该结果即表示应将此键值对存储到的桶的编号;
    • 建立一个新节点存储此键值对,同时将该节点链接到相应编号的桶上。
  • 另外值得一提的是,哈希表存储结构还有一个重要的属性,称为负载因子(load factor)。该属性同样适用于无序容器,用于衡量容器存储键值对的空/满程序,即负载因子越大,意味着容器越满,即各链表中挂载着越多的键值对,这无疑会降低容器查找目标键值对的效率;反之,负载因子越小,容器肯定越空,但并不一定各个链表中挂载的键值对就越少。
  • 举个例子,如果设计的哈希函数不合理,使得各个键值对的键带入该函数得到的哈希值始终相同(所有键值对始终存储在同一链表上):这种情况下,即便增加桶数是的负载因子减小,该容器的查找效率依旧很差;
  • 无序容器中,负载因子的计算方法为:
负载因子 = 容器存储的总键值对 / 桶数
  • 默认情况下,无序容器的最大负载因子为 1.0。如果操作无序容器过程中,使得最大复杂因子超过了默认值,则容器会自动增加桶数,并重新进行哈希,以此来减小负载因子的值。需要注意的是,此过程会导致容器迭代器失效,但指向单个键值对的引用或者指针仍然有效。这也就解释了,为什么在操作无序容器过程中,键值对的存储顺序有时会“莫名”的发生变动。
  • C++ STL 标准库为了方便用户更好地管控无序容器底层使用的哈希表存储结构,各个无序容器的模板类中都提供如下所示的成员方法:
成员方法功能
bucket_count()返回当前容器底层存储键值对时,使用桶的数量
max_bucket_count()返回当前系统中,unordered_map 容器底层最多可以使用多少个桶
bucket_size(n)返回第 n 个桶中存储键值对的数量
bucket(key)返回以 key 为键的键值对所在桶的编号
load_factor()返回 unordered_map 容器中当前的负载因子
max_load_factor()返回或者设置当前 unordered_map 容器的最大负载因子
rehash(n)尝试重新调整桶的数量为等于或大于 n 的值,如果 n 大于当前容器使用的桶数,则该方法会是容器重新哈希,该容器新的桶数将等于或大于 n;反之,如果 n 的值小于当前容器使用的桶数,则调用此方法可能没有任何作用
reserve(n)将容器使用的桶数(bucket_count() 方法的返回值)设置为最适合存储 n 个元素的桶数
hash_function()返回当前容器使用的哈希函数对象
  • 以 unordered_map 容器为例,演示上表中的部分成员方法用法:
#include <iostream>
#include <string>
#include <unordered\_map>
using namespace std;
int main() {
    // 创建空 umap 容器
    unordered\_map<string, string> umap;

    cout << "umap 初始桶数: " << umap.bucket\_count() << endl;
    cout << "umap 初始负载因子: " << umap.load\_factor() << endl;
    cout << "umap 最大负载因子: " << umap.max\_load\_factor() << endl;

    // 设置 umap 使用最适合存储 9 个键值对的桶数
    umap.reserve(9);
    cout << "\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*" << endl;
    cout << "umap 新桶数: " << umap.bucket\_count() << endl;
    cout << "umap 新负载因子: " << umap.load\_factor() << endl;
    // 向 umap 容器添加 3 个键值对
    umap\["Python教程"\] = "http://c.biancheng.net/python/";
    umap\["Java教程"\] = "http://c.biancheng.net/java/";
    umap\["Linux教程"\] = "http://c.biancheng.net/linux/";
    // 调用 bucket() 获取指定键值对位于桶的编号
    cout << "以\\"Python教程\\"为键的键值对,位于桶的编号为:" << umap.bucket("Python教程") << endl;
    // 自行计算某键值对位于哪个桶
    auto fn = umap.hash\_function();
    cout << "计算以\\"Python教程\\"为键的键值对,位于桶的编号为:" << fn("Python教程") % (umap.bucket\_count()) << endl;
    return 0;
}
  • 程序执行结果为:
umap 初始桶数: 8  
umap 初始负载因子: 0  
umap 最大负载因子: 1  
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*  
umap 新桶数: 16  
umap 新负载因子: 0"Python教程"为键的键值对,位于桶的编号为:9  
计算以"Python教程"为键的键值对,位于桶的编号为:9
  • 从输出结果可以看出,对于空的 umap 容器,初始状态下会分配 8 个桶,并且默认最大负载因子为 1.0,但由于其为存储任何键值对,因此负载因子值为 0。
  • 与此同时,程序中调用 reverse() 成员方法,是 umap 容器的桶数改为了 16,其最适合存储 9 个键值对。从侧面可以看出,一旦负载因子大于 1.0(9/8 > 1.0),则容器所使用的桶数就会翻倍式(8、16、32、…)的增加。
  • 程序最后还演示了如何手动计算出指定键值对存储的桶的编号,其计算结果和使用 bucket() 成员方法得到的结果是一致的。

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

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

相关文章

JVM 垃圾收集器详解

一、垃圾收集器 如果说收集算法是内存回收的方法论&#xff0c;那垃圾收集器就是内存回收的实践者。《Java虚拟机规范》中对垃圾收集器应该如何实现并没有做出任何规定&#xff0c;因此不同的厂商、不同版本的虚拟机所包含的垃圾收集器都可能会有很大差别&#xff0c;不同的虚…

基于遗传算法的中药药对挖掘系统的设计与实现

用数据挖掘技术研究了中药方剂配伍的规律。主要工作&#xff1a;分析了关联规则存在的问题&#xff0c;引入双向关联规则的概念&#xff1b;介绍了遗传算法的基本原理&#xff0c;研究了遗传算法在数据挖掘中的应用&#xff1b;将方剂库转换为位图矩阵&#xff0c;大大提高搜索…

Mac重启清理缓存会怎么样 mac清理缓存怎么清理

众所周知&#xff0c;Mac电脑有着流畅的操作系统&#xff0c;因此&#xff0c;很多用户都会选择使用Mac电脑办公。随着日常使用&#xff0c;系统缓存数据越来越大&#xff0c;某些Mac电脑&#xff08;尤其是小内存版本的Mac电脑&#xff09;可能会出现“系统”占存储空间比例较…

初始单片机.md

1.如何将HEX文件烧录到单片机 STC-ISP STC-ISP是一款单片机下载编程烧录软件&#xff0c;是针对STC系列单片机而设计的&#xff0c;可下载STC89系列、12C2052系列和12C5410等系列的STC单片机&#xff0c;使用简便。 思路&#xff1a;将电脑磁盘上已存在的文件通过串口的方式下…

python中第三方库xlrd和xlwt的使用教程

excel文档名称为联系人.xls&#xff0c;内容如下&#xff1a; 一、xlrd模块用法 1.打开excel文件并获取所有sheet import xlrd# 打开Excel文件读取数据 data xlrd.open_workbook(联系人.xls)sheet_name data.sheet_names() # 获取所有sheet名称 print(sheet_name) # [银…

python依次运行多个代码遇到的同步与异步问题

1、要实现在一个Python代码运行完后紧接着运行另一个Python代码&#xff0c;可以使用Python的subprocess模块。该模块可以创建新进程并与之交互&#xff0c;可以用于在Python代码中启动新的程序或脚本。 下面是一个示例代码&#xff0c;用于在运行完code1.py后紧接着运行code2…

论文配色方案(收藏)

方案一&#xff1a;复古 系列色——十六进制颜色码&#xff1a; 0780cf - 765005 - fa6d1d - 0e2c82 - b6b51f - da1f18 - 701866 - f47a75 - 009db2 - 024b51 - 0780cf - 765005 系列色——RGB颜色值&#xff1a; &#xff08;7,128,207&#xff09;-&#xff08;118,80,5&…

【记录】优化油猴插件【BD网盘播放器】

收获最重要前言优化代码收获1.禁止浏览器弹窗2.定时器3.设置属性-隐藏元素4.鼠标悬停和移开事件5.添加元素6.div里均匀分布7.获取元素属性值8.监听播放器状态9.jQuery 选择器前言 有钱的可以支持一下原作者&#xff0c;没钱就要多学习 优化 1.1 视频刚开始未播放时&#xff…

酷雷曼一站式图片直播,助力品牌高效传播

传统模式下&#xff0c;摄影师拍摄会议、活动现场的照片后&#xff0c;一般需要7-10天时间才能完成成片交付&#xff0c;而实际上&#xff0c;由于新闻宣传的即时性&#xff0c;照片延迟交付&#xff0c;远远不能满足客户的需求。因此&#xff0c;即时图片直播技术应运而生&…

Jenkins自动化部署实例讲解

文章目录 前言实例讲解基本环境全局工具配置创建任务任务配置源码管理构建步骤&#xff08;Build Steps&#xff09;第一步&#xff1a;调用Maven第二步&#xff1a;执行shell启动容器 后记 前言 你平常在做自己的项目时&#xff0c;是否有过部署项目太麻烦的想法&#xff1f;…

哈希应用——位图(bitset)

目录 见见猪跑(初步了解位图) 位图的模拟实现 位图的应用 1、给定100亿个整数&#xff0c;设计算法找到只出现一次的整数 2、给两个文件&#xff0c;分别有100亿个整数&#xff0c;我们只有1G内存&#xff0c;如何找到两个文件交集&#xff1f; 3、位图应用变形&#xff…

第六章 Gated RNN

目录6.1 RNN的问题6.1.1 RNN的复习6.1.2 梯度消失和梯度爆炸6.1.3 梯度消失和梯度爆炸的原因6.1.4 梯度爆炸的对策6.2 梯度消失和LSTM6.2.1 LSTM的接口6.2.2 LSTM层的结构6.2.3 输出门6.2.4 遗忘门6.2.5 新的记忆单元6.2.6 输入门6.2.7 LSTM的梯度的流动6.3 LSTM的实现6.4 使用…

CIO成就计划第二季第一期 | 活动回顾:基于数字技术重塑流程,驱动业务增长

2023年3月25日&#xff0c;《科创人》联手金蝶共同打造的【CIO成就计划第二季】首期培训在北京金蝶软件园圆满举行。 【CIO成就计划】第二季&#xff0c;坚持服务于企业CIO、数字化变革负责人的价值定位&#xff0c;通过三期线下闭门培训会社群交流&#xff0c;帮助CIO建立应对…

JUC高级十-AbstractQueuedSynchronizer之AQS

1. 前置知识 公平锁和非公平锁可重入锁自旋锁LockSupport数据结构之双向链表设计模式之模板设计模式 AQS重要性 JAVA ------>JVM AQS ------>AQS 2. AQS入门级别理论知识 2.1 是什么? 2.1.1 字面意思 Abstract Queued Synchronizer----抽象的队列同步器 源码位置: …

【Java 并发编程】一文读懂线程、协程、守护线程

一文读懂线程、协程、守护线程1. 线程的调度1.1 协同式线程调度1.2 抢占式线程调度1.3 设置线程的优先级2. 线程的实现模型和协程2.1 内核线程实现2.2 用户线程实现2.3 混合实现2.4 Java 线程的实现2.5 协程2.5.1 出现的原因2.5.2 什么是协程2.5.3 Java19 虚拟线程 - 协程的复苏…

Cat原理简析

Cat原理简析 链路追踪系统设计思路如何高效组织业务日志如何动态串联业务日志通用解决方案链路定义链路染色链路上报链路存储 Cat原理客户端原理API设计序列化和通信客户端埋点核心类分析流程分析启动流程:消息生产Context 线程本地变量Transaction事务的开启其他类型消息组合关…

网页设计方向有哪些SCI期刊推荐? - 易智编译EaseEditing

网页设计和开发方向主要涉及人机交互、用户体验、可访问性等方面&#xff0c;以下是几个相关的SCI期刊推荐&#xff1a; ACM Transactions on Computer-Human Interaction (ACM TOCHI)&#xff1a; 该期刊由ACM&#xff08;Association for Computing Machinery&#xff09;出…

一次etcd变更引发的惨案

问题描述 在做etcd的数据变更时候&#xff0c;etcd在组成集群的时候出现leader不断切换问题&#xff0c;导致集群不稳定&#xff0c;都面将不健康的etcd节点踢出&#xff0c;只剩etcd单节点&#xff0c;后面将踢出的etcd节点重新加入现有etcd&#xff0c;导致etcd集群奔溃&…

【java踩坑搞起】MybatisPlus封装的mapper不支持 join,那咋办

众所周知&#xff0c;Mybatis Plus 封装的 mapper 不支持 join&#xff0c;如果需要支持就必须自己去实现。但是对于大部分的业务场景来说&#xff0c;都需要多表 join&#xff0c;要不然就没必要采用关系型数据库了。 直到前几天&#xff0c;偶然碰到了这么一款叫做mybatis-p…

权限提升:网站后台.(提权思路.)

权限提升&#xff1a;网站后台 权限提升简称提权&#xff0c;由于操作系统都是多用户操作系统&#xff0c;用户之间都有权限控制&#xff0c;比如通过 Web 漏洞拿到的是 Web 进程的权限&#xff0c;往往 Web 服务都是以一个权限很低的账号启动的&#xff0c;因此通过 Webshell …