java 与 c++在遍历 map 数据结构上的一些差异

news2025/3/4 22:28:27

在编写动态规划时,发现了一个现象:

  • C++ 中的 unordered_mapmap 可以一边遍历一边添加数据
  • Java 中的 HashMap 却不能,Java 只能通过 ConcurrentSkipListMap 实现在遍历中添加数据。

问了 grok,原来是两个编程语言在 map 数据结构底层设计的不同,总结如下。

文章目录

    • 1. `map` 和 `unordered_map` 的迭代器行为
      • (1) `std::map`(红黑树实现)
      • (2) `std::unordered_map`(哈希表实现)
    • 2. 代码示例
      • `std::map` 示例
      • `std::unordered_map` 示例
    • 3. 与 Java `HashMap` 的对比
      • 关键差异
    • 4. 注意事项
    • 5. 为什么与 Java `ConcurrentSkipListMap` 不同?
    • 6. 总结

在 C++ 中,unordered_mapmap 在遍历时一边添加数据(即插入新元素)之所以是可行的,是因为它们的迭代器设计和底层数据结构的特性允许这种操作,而不会像 Java 的 HashMap 那样抛出类似 ConcurrentModificationException 的异常。以下是对这一现象的详细解释,以及为什么它们在遍历中添加数据时表现不同的原因。


1. mapunordered_map 的迭代器行为

C++ 标准库中的容器(如 std::mapstd::unordered_map)遵循明确的迭代器失效规则。这些规则决定了在修改容器时,现有迭代器是否会变为无效(invalid)。关键点在于:插入操作不会使已有迭代器失效

(1) std::map(红黑树实现)

  • std::map 是一个有序关联容器,底层基于红黑树(一种自平衡二叉搜索树)。
  • 迭代器失效规则
    • 插入(insert)或删除(erase)操作不会使指向已有元素的迭代器失效。
    • 只有当某个具体元素被删除时,指向该元素的迭代器才会失效。
  • 为什么可以一边遍历一边添加
    • 在遍历 map 时(例如使用 for 循环或迭代器),插入新元素只会调整红黑树的结构,但不会影响已有节点的内存地址或迭代器的有效性。
    • 插入操作的时间复杂度是 O(log n),但不会破坏正在进行的遍历。

(2) std::unordered_map(哈希表实现)

  • std::unordered_map 是一个无序关联容器,底层基于哈希表(通常是链地址法)。
  • 迭代器失效规则
    • 插入(insert)操作不会使已有迭代器失效,除非触发了哈希表的重新散列(rehash)。
    • 如果插入导致哈希表容量不足(负载因子超过阈值),会触发 rehash,此时所有迭代器都会失效。
  • 为什么可以一边遍历一边添加
    • 在不触发 rehash 的情况下,插入新元素只会修改某个桶的链表(或类似结构),而不影响其他桶中已有元素的迭代器。
    • 如果遍历时小心避免 rehash(例如预先用 reserve 设置足够容量),就可以安全添加数据。

2. 代码示例

以下是 C++ 中 mapunordered_map 在遍历时添加数据的例子:

std::map 示例

#include <iostream>
#include <map>

int main() {
    std::map<int, int> m = {{1, 10}, {2, 20}};
    
    for (auto it = m.begin(); it != m.end(); ++it) {
        if (it->first == 2) {
            m.insert({3, 30}); // 在遍历时插入新元素
        }
        std::cout << it->first << ": " << it->second << "\n";
    }
    
    // 输出完整 map
    for (auto [key, value] : m) {
        std::cout << key << ": " << value << "\n";
    }
    return 0;
}

输出

1: 10
2: 20
1: 10
2: 20
3: 30
  • 插入 3: 30 不会影响已有迭代器,遍历继续正常进行。

std::unordered_map 示例

#include <iostream>
#include <unordered_map>

int main() {
    std::unordered_map<int, int> um = {{1, 10}, {2, 20}};
    um.reserve(10); // 预留空间,避免 rehash
    
    for (auto it = um.begin(); it != um.end(); ++it) {
        if (it->first == 2) {
            um.insert({3, 30}); // 在遍历时插入新元素
        }
        std::cout << it->first << ": " << it->second << "\n";
    }
    
    // 输出完整 unordered_map
    for (auto [key, value] : um) {
        std::cout << key << ": " << value << "\n";
    }
    return 0;
}

输出(顺序可能不同,因为是无序的):

1: 10
2: 20
1: 10
2: 20
3: 30
  • 只要不触发 rehash(通过 reserve 预留空间),插入不会使迭代器失效。

3. 与 Java HashMap 的对比

Java 的 HashMap 在遍历时不允许修改(添加或删除),否则会抛出 ConcurrentModificationException。这是因为:

  • Java 的 HashMap 使用**快速失败(fail-fast)**迭代器设计。一旦检测到结构修改(通过修改计数器 modCount),迭代器会立即抛出异常。
  • C++ 的 mapunordered_map 没有类似的机制,它们的迭代器是基于指针的惰性设计,只要内存地址有效,迭代器就可用。

关键差异

  • Java: 检查结构性修改(putremove 等改变大小的操作),主动抛出异常。
  • C++: 只关心迭代器指向的元素是否仍然有效,插入操作通常不会影响已有元素。

4. 注意事项

尽管 C++ 的 mapunordered_map 允许一边遍历一边添加数据,但仍需注意以下问题:

  • map
    • 如果在遍历中插入大量元素,可能会导致性能下降(每次插入 O(log n))。
    • 如果删除元素,需小心处理当前迭代器(例如使用 erase 返回的新迭代器)。
  • unordered_map
    • 如果插入触发 rehash,所有迭代器都会失效,导致未定义行为(undefined behavior)。可以用 reserve 提前分配空间来避免。
    • 示例(触发 rehash 的错误情况):
      std::unordered_map<int, int> um;
      for (auto it = um.begin(); it != um.end(); ++it) {
          um.insert({um.size() + 1, 0}); // 可能触发 rehash,迭代器失效
      }
      

5. 为什么与 Java ConcurrentSkipListMap 不同?

你提到 Java 的 ConcurrentSkipListMap 在递归中不出错,而 HashMap 出问题。C++ 的 mapunordered_mapConcurrentSkipListMap 类似的一点是,它们在设计上都支持一定的修改容忍度:

  • ConcurrentSkipListMap 是线程安全的,支持并发修改,迭代器是弱一致性的。
  • C++ 的 mapunordered_map 虽不是为多线程设计的,但在单线程下,插入操作不会破坏迭代器的有效性。

但与 Java 的 HashMap 不同,C++ 不强制快速失败,而是将行为交给开发者控制。


6. 总结

  • std::map:基于红黑树,插入不会使迭代器失效,因此可以一边遍历一边添加。
  • std::unordered_map:基于哈希表,只要不触发 rehash,也可以一边遍历一边添加。
  • 与 Java 的区别:C++ 的迭代器设计更宽松,没有快速失败机制,开发者需自己保证正确性。

另外:

  • Java 的 HashMap 在容量不足时也会触发类似 rehash 的扩容(resize),但它的快速失败机制会在遍历时检测到修改并抛出异常。
  • C++ 的 unordered_map 没有这种保护机制,rehash 后迭代器失效是程序员的责任。

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

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

相关文章

vscode通过ssh远程连接(linux系统)不能跳转问题

1.问题描述 unbantu中的vscode能够通过函数跳转到函数定义&#xff0c;而windows通过ssh连接unbantu的vscode却无法跳转 2.原因&#xff1a; 主要原因是这里缺少插件&#xff0c;这里是unbantu给主机的服务器&#xff0c;与ubantu本地vscode插件相互独立&#xff0c;能否跳转…

unity pico开发 五 UI交互

文章目录 添加画布添加交互组件取消传送射线对UI的控制解决按扳机键会传送的冲突按下按键呼出菜单&#xff0c;并让菜单出现在头的前方 添加画布 创建一个新画布&#xff0c;添加一个Button&#xff0c;将画布改为world space&#xff0c;然后缩放改为0.001&#xff0c;调整到…

软开经验总结

文章目录 软开经验总结一、二次开发时候操作步骤二、logger的作用&#xff01;&#xff01;&#xff01;三、git使用 软开经验总结 一、二次开发时候操作步骤 改 SDK 和 language level改 maven 配置改数据库 注意Mysql 版本 差别是否过大&#xff01;&#xff01;&#xff0…

QT 中的元对象系统(三):QObject深入理解

目录 1.简介 2.特性 2.1.对象树与内存管理 2.2.信号与槽机制 2.3.事件处理 2.4.属性系统 2.4.1.Q_PROPERTY配置的属性 2.4.2.动态属性 2.4.3.实现原理 2.5.国际化支持 2.6. 定时器支持 3.类设计(q和d指针) 4.总结 1.简介 QObject这个 class 是 QT 对象模型的核心&…

二、QT和驱动模块实现智能家居-----问题汇总1

1、文件地址改变后必须在QT下更改地址 2、指定了QT内Kits下的Sysroot头文件地址&#xff0c;但是还是找不到头文件&#xff1a; 3、提示无法执行QT程序&#xff1a;先干掉之前的QT程序 ps //查看程序PIDkill -9 PID 4、无法执行QT程序 1&#xff09;未设置环境变量 …

Golang的数据库分库分表

# Golang的数据库分库分表 什么是数据库分库分表 数据库分库分表是指将单一的数据库拆分成多个库&#xff0c;每个库中包含多张表&#xff0c;以提高数据库的性能和可伸缩性。通常在大型应用中&#xff0c;单一的数据库往往无法满足高并发和海量数据的需求&#xff0c;因此需要…

NModbus 连接到Modbus服务器(Modbus TCP)

1、在项目中通过NuGet添加NModbus&#xff0c;在界面中添加一个Button。 using NModbus.Device; using NModbus; using System.Net.Sockets; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Docu…

基于vue3和flask开发的前后端管理系统(一):项目启动准备

准备工作 我们需要准备以下工具 vue3&#xff1a;构建前端 tailwind css&#xff1a;样式库vite&#xff1a;快速构建vue项目pinia &#xff1a;vue3 的事件管理器 flask&#xff1a;后端代码Mysql&#xff1a;数据库 heidisql&#xff1a;数据库图形化界面 vscode&#xff1…

单例模式(线程案例)

单例模式可以分为两种&#xff1a;1.饿汉模式 2.懒汉模式 一.饿汉模式 //饿汉模式&#x1f447; class MySingleTon{//因为这是一个静态成员变量&#xff0c;在类加载的时候&#xff0c;就创建了private static MySingleTon mySingleTon new MySingleTon();//创建一个静…

通过多线程分别获取高分辨率和低分辨率的H264码流

目录 一.RV1126 VI采集摄像头数据并同时获取高分辨率码流和低分辨率码流流程 ​编辑 1.1初始化VI模块&#xff1a; 1.2初始化RGA模块&#xff1a; 1.3初始化高分辨率VENC编码器、 低分辨率VENC编码器&#xff1a; 1.4 VI绑定高分辨率VENC编码器&#xff0c;VI绑定RGA模块…

智慧农业中光谱相机对土壤成分的无损检测应用‌

可浏览之前发布的一篇文章&#xff1a;光谱相机在农业中的具体应用案例 一、土壤成分定量分析 ‌养分检测‌ 光谱相机通过捕捉土壤反射的特定波长光线&#xff0c;可精准检测氮、磷、钾等主要养分含量&#xff0c;以及有机质和水分比例。例如&#xff0c;不同养分对近红外波段…

DNS 详细过程 与 ICMP

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Linux 目录 一&#xff1a;&#x1f525; DNS (Domain Name System) 快速了解&#x1f98b; DNS 背景&#x1f98b; 域名简介&#x1f98b; 真实地址查询 —— DNS&#x1f380; 域名的层级关系&am…

学到什么记什么(25.3.3)

Upload-labs 今日重新做了一下文件上传漏洞&#xff0c;这里第一题之前采用直接抓包改后缀名.jpg为.php&#xff0c;再写入一句话<?php phpinfo();?>然后放行&#xff0c;得到图片地址&#xff08;可复制&#xff09;&#xff0c;本来直接访问图片地址即可得到敏感信息…

阿里云服务器部署项目笔记 实操 centos7.9

阿里云服务器部署项目笔记 实操 centos7.9 springboot vue elementUImysqlredis 相关的redis,mysql,nginx镜像,jdk 通过网盘分享的文件&#xff1a;docker镜像 链接: https://pan.baidu.com/s/15VwcWBP4Jy07xADuvylgQw?pwdm2g9 提取码: m2g9 配置环境 连接云服务器 安装…

win32汇编环境,窗口程序中使控件子类化的示例一

;运行效果 ;win32汇编环境,窗口程序中使编辑框控件子类化的示例一 ;窗口子类化&#xff0c;就是把某种控件&#xff0c;自已再打造一遍&#xff0c;加入自已的功能。比如弄个特殊形状的按钮&#xff0c;或只能输入特殊字符的编辑框 ;当然&#xff0c;一般来说&#xff0c;这都是…

多镜头视频生成、机器人抓取、扩散模型个性化 | Big Model weekly第58期

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 01 GLM-4-Voice: Towards Intelligent and Human-Like End-to-End Spoken Chatbot 本文介绍了一种名为GLM-4-Voice的智能且类人化的端到端语音聊天机器人。它支持中文和英文&#xff0c;能够进行实时语音对话&a…

第十四届蓝桥杯:(二分算法)字串简写

这道题我们的做法是开两个vector&#xff0c;分别把a和b字符的下标存进去&#xff0c;然后遍历a字符&#xff0c;我们要求长度必须大于等于k&#xff0c;我们可以画个图&#xff0c;也就是说b的下标减a的下标必须大于等于k-1 也就是b的下标必须大于等于a的下标k-1 我们用二分找…

制服小程序的“滑手”:禁用页面左右滑动全攻略

哈哈&#xff0c;看来你已经很聪明地发现了小程序中左右滑动的“顽皮”行为&#xff01;&#x1f604; 没错&#xff0c;我们可以通过设置 disableScroll 属性来“管教”它&#xff0c;同时结合 CSS 样式让页面既禁得住横向“乱跑”&#xff0c;又能顺畅地上下滚动。你的方案已…

webstorm的Live Edit插件配合chrome扩展程序JetBrains IDE Support实现实时预览html效果

前言 我们平时在前端网页修改好代码要点击刷新再去看修改的效果&#xff0c;这样比较麻烦&#xff0c;那么很多软件都提供了实时预览的功能&#xff0c;我们一边编辑代码一边可以看到效果。下面说的是webstorm。 1 Live Edit 首先我们需要在webstorm的settings里安装插件Live …

02 HarmonyOS Next仪表盘案例详解(一):基础篇

温馨提示&#xff1a;本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦&#xff01; 文章目录 1. 项目概述2. 技术架构2.1 文件结构2.2 ArkTS 语言特性装饰器的使用 3. 数据结构设计3.1 接口定义3.2 数据初始化 4. 生命周期与页面路由…