ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Qt QHash深度解析:核心用法、底层原理与高效实践指南

Qt QHash深度解析:核心用法、底层原理与高效实践指南 1. QHash到底是什么为什么值得用如果你用Qt做开发肯定绕不过QHash这个容器。简单说QHash是一个基于哈希表Hash Table实现的关联容器它存储的是“键值对”给你一个键你能立刻找到对应的值。这个“立刻”很重要因为它的平均查找时间是常数级也就是常说的O(1)无论里面存了100条数据还是100万条数据单次查找的耗时几乎不变。第一次接触哈希表的人可能会有点懵这里我用生活里最常见的场景来解释你去图书馆借书每本书都有一个索书号管理员把索书号和书架位置做成一张表。你要找《百年孤独》先查表看到索书号然后直奔对应书架整个过程不用把图书馆从头到尾扫一遍。哈希表干的就是这么一件事——通过一个函数哈希函数把“键”直接换算成“存储位置”取的时候再按同样方法换算一步到位。相比Qt里另一个常用的关联容器QMap底层是红黑树QHash有几个非常明显的优势插入和查找速度更快、内存占用在大多数场景下更低、支持自定义类型的灵活度更高。但代价是它不保证元素顺序如果你需要按键排序遍历QMap才是绝对正确的选择。后面我会详细对比这两个容器这里先不展开。这篇文章的目标很明确我会从插入、取值、遍历、删除四个核心操作入手把QHash用法的关键细节全拆开讲清楚再附上一些我实际开发中踩过的坑和排查经验。无论是刚接触Qt的初学者还是写了一阵子Qt但没系统研究过QHash的人看这篇文章都会收获一些能直接用到项目里的东西。2. 插入操作从基础到细节2.1 最常用的三种插入方式QHash的插入操作常见的有三种方式分别是insert()、operator[]和insertMulti()。初学者最常用的应该是前两种但它们的底层行为有细微差别用错了会出现难以察觉的bug。先看最基本的上代码QHashQString, int hash; // 方式一insert hash.insert(apple, 3); hash.insert(banana, 5); // 方式二operator[] hash[orange] 2; hash[grape] 4;insert()和operator[]乍看效果一样都能把一个键值对放进去。但区别在于如果用insert()向已有相同键的条目插入新值旧值会被直接覆盖而operator[]在键不存在时会自动创建默认值然后你再给它赋值这在语义上多了一步“先默认构造再赋值”。实际项目中我推荐无脑用insert()因为它语义明确不会在构造函数上有额外开销也方便以后做代码审查时一眼看清意图。如果你确实需要“键不存在就创建并插入键存在就更新值”这种原子性操作operator[]在Qt的隐式共享机制下其实也很高效不必过于担心性能损耗。再看insertMulti()这个方法的用途是允许同一个键对应多个值。比如一个班级里可能有多个同名同姓的学生你直接用insert()的话后插入的会把前一个覆盖掉但insertMulti()不会它会保留所有同键的值。QHashQString, int multiHash; multiHash.insertMulti(grade, 90); multiHash.insertMulti(grade, 85); multiHash.insertMulti(grade, 95); // 此时multiHash.count(grade) 3需要强调一点insertMulti()生产环境尽量少用。因为取这种多值数据时你得用values(key)拿一个QList出来逻辑一下子就复杂了。更优雅的方案是用QHashQString, QListint这种“键对应列表”的结构代码可读性和操作便利性都远超insertMulti。我自己早期项目里用过insertMulti后来重构时全改成了嵌套容器体感差距很大。2.2 插入时背后的哈希计算逻辑每次你调用insert(key, value)QHash内部不是直接把键存进一个有序链表里而是先通过哈希函数把键换算成一个整数哈希值再根据这个整数定位到桶bucket的位置。这就是为什么查找能快到接近O(1)。Qt内置了许多常用类型的哈希函数比如QString、int、QByteArray、QDate、QUuid等开箱即用不需要你额外写任何代码。但如果你自己定义了一个结构体或类作为键就必须告诉QHash“怎么给这个类型算哈希”以及“怎么判断两个键相等”。这部分内容我放到第6节专门讲因为这是所有自定义类型使用QHash时最容易踩坑的地方很多人写的代码编译都过不了或者过了但逻辑错误。哈希值相同但键不同这在哈希表里叫“碰撞”。QHash内部采用链地址法处理碰撞也就是多个键落在同一个桶时它们会以链表形式串起来。正常情况下碰撞概率很低但碰撞多了会影响查找性能。所以自定义哈希函数时尽量让哈希值分布均匀尽量避免所有键都算出同一个数这种极端情况。2.3 关于容量预留与批量插入的小经验QHash底层是动态扩容的当你不断往里插入数据它会自动调整内部桶的数量。但这个扩容不是免费的它会触发一次全量元素的重新哈希和内存重排数据量越大单次扩容的代价越高。如果提前知道大概要存多少条数据可以用reserve()提前分配容量QHashint, QString hash; hash.reserve(10000); // 预分配能容纳约10000个元素的桶 for (int i 0; i 10000; i) { hash.insert(i, QString::number(i)); }这有点类似QVector::reserve()和std::vector::reserve()的语义目的都是尽量减少插入过程中因扩容导致的多次内存分配。实测情况下对于超过10万条的批量插入场景先reserve()能省下20%到30%的耗时效果很可观。另外批量插入时还有一个细节QHash采用隐式共享Copy-on-Write机制如果你把哈希表作为参数传进某个函数并在里面大量插入尽量以引用的方式传递QHashK, V否则每次传值都会触发一次深拷贝的隐患——虽然是懒拷贝但修改时会复制整个底层数组。这个坑我见很多人踩过后面讲常见问题时我会专门展开。3. 取值操作拿到数据只是第一步3.1 value、valueOrDefault、operator[]的区别插入是存取值是取。取值看似简单但QHash提供了好几个接口每个接口对“键不存在”的处理方式完全不同这一步选错轻则返回一个无害的默认值重则直接崩溃。先看最常用的两种QHashQString, int hash; hash.insert(apple, 3); // 方式一value(key) int v1 hash.value(apple); // v1 3 int v2 hash.value(not_exist); // v2 0键不存在时返回默认构造值 // 方式二value(key, defaultValue) int v3 hash.value(not_exist, -1); // v3 -1指定默认值 // 方式三operator[] int v4 hash[apple]; // v4 3 int v5 hash[not_exist]; // 问题来了value()和value()传入默认值这两个版本都是安全的键不存在时不会改变哈希表本身只是返回一个默认构造的值或者你指定的值。而operator[]在键不存在时会做一件很多人没想到的事——它会在哈希表里创建一个键值对值就是默认值。这就是所谓的“非const操作”。我把这个行为称之为“读操作写出了副作用”。很多项目里的bug就是这么来的在if (hash[key] 1)这种判断语句里如果key不存在它不会返回一个临时默认值而是真的往哈希表里插入了一条数据。你明明只是想读结果把表给改了后面遍历时突然多出一个键排查起来非常抓狂。所以我的铁律是只读场景一律用value(key)或value(key, defaultValue)永远不要用operator[]来做只读判断。operator[]只用来做下面这种明确的写操作hash[apple] 10; // 键存在就更新键不存在就创建语义明确可用如果你想在取值前先检查键是否存在用contains()不要用operator[]做检查if (hash.contains(apple)) { int value hash.value(apple); }3.2 查找类操作与“取空”问题很多时候你不需要直接把值取出来只需要确认某个键是否存在于哈希表中或者你想通过值反向找键。QHash为此提供了contains()、key()、keys()、values()等接口。QHashQString, int hash; hash.insert(apple, 3); hash.insert(banana, 5); bool hasApple hash.contains(apple); // true QString k hash.key(5); // banana根据值查键 QString k2 hash.key(100); // QString()空字符串因为找不到 QString k3 hash.key(100, default_key); // default_key指定找不到时的默认键key(value)这种反向查找值得注意因为它内部是遍历整张表逐个比较值的时间复杂度是O(n)不是哈希查找的O(1)。如果哈希表有几万条数据频繁反向查找会明显拖慢性能。要反复反向查找的话最好单独维护一张“值到键”的反向哈希表。还有一个容易被忽略的“取空”场景如果你存进去的值本身就是一个“无意义”的默认值那么contains()判存在仍然返回truevalue()取出来的还是这个默认值。比如hash.insert(flag, 0)你后面用if (hash.value(flag))这种写法来判断逻辑状态实际上0会被当成false逻辑上很容易出问题。根本原因是哈希表里存的是键值对它不会因为值为0或空字符串就认为这个条目不合法。这种场景的判断标准应该是“键是否存在”用contains()最可靠而不是看值是否为空。4. 遍历操作三种遍历姿势逐一对比4.1 Java风格迭代器遍历QHash和QMap都支持Java风格的迭代器QHashIterator这种迭代器的特点是“指向两个元素之间”而不是“指向某个元素”。使用时你需要先findNext()或findPrevious()移动迭代器然后通过key()和value()取当前元素。看代码QHashQString, int hash; hash.insert(apple, 3); hash.insert(banana, 5); hash.insert(orange, 2); QHashIteratorQString, int it(hash); while (it.hasNext()) { it.next(); qDebug() it.key() : it.value(); }Java风格迭代器的最大优点是语法直观符合很多从Java转过来的开发者的习惯而且它在遍历中删除元素非常安全后面讲删除时会详细介绍因为remove()方法就是为迭代过程设计的。缺点是比STL风格的迭代器稍慢一点点因为多了一层封装但实际项目里这点性能差异基本可以忽略。我个人看法如果你在团队里写代码队友以Qt新手为主优先用Java风格迭代器可读性更高如果项目对遍历性能非常敏感或者说你更喜欢C标准库的编程习惯那就用STL风格。4.2 STL风格迭代器遍历STL风格的迭代器跟C标准库里的std::unordered_map迭代器用法几乎一致用begin()和end()标记遍历范围用it移动迭代器QHashQString, int::const_iterator it; for (it hash.constBegin(); it ! hash.constEnd(); it) { qDebug() it.key() : it.value(); } // 如果需要修改值用非const迭代器 QHashQString, int::iterator it2; for (it2 hash.begin(); it2 ! hash.end(); it2) { it2.value() 1; // 所有值加1 }STL风格迭代器的关键点在于const_iterator和iterator的区分。如果你只是想读数据一定用constBegin()和constEnd()配合const_iterator。原因有两个一是非const的begin()返回的迭代器允许修改内容如果你没打算改却拿到了可写迭代器编译器不会报错但语义上容易埋雷二是在某些情况下非const操作会触发Qt容器的隐式共享分离detach导致不必要的深拷贝影响性能。我见过不少老代码遍历都用iterator其实完全没必要改成const_iterator后性能和安全性都更好。4.3 范围for与foreach宏遍历Qt从早期版本就提供了foreach宏严格说是QtCore里的一个宏后来C11普及后又支持了范围for循环。这两种写法的代码最简洁// 范围for推荐 QHashQString, int hash; hash.insert(apple, 3); hash.insert(banana, 5); for (const QString key : hash.keys()) { qDebug() key : hash.value(key); } // 如果要同时拿键和值Qt官方还提供了结构化绑定写法需要C17 for (const auto [key, value] : hash.asKeyValueRange()) { qDebug() key : value; }这里有个非常关键的坑hash.keys()会返回一个QListQString这个列表是键的一份拷贝副本然后你遍历的是这个副本再通过hash.value(key)去查值。这种方式思路简单但存在两个问题一是多了一次QList的内存分配和拷贝二是如果你在遍历过程中对哈希表做了修改插入或删除这个副本跟哈希表内部状态可能不一致逻辑上很容易出问题。我推荐的写法是上面代码里的asKeyValueRange()它是Qt 5.14引入的新接口能在不创建键列表拷贝的情况下直接遍历所有键值对性能好、代码简洁还能配合C17的结构化绑定直接解包键和值。我自己的项目至少在Qt 5.15以上所以遇到类似场景基本都用这个写法。再补充一下foreach宏的注意事项虽然它写起来非常方便早期Qt文档大量使用但Qt官方从Qt 5.7开始已经不建议在Qt代码里使用这个宏了原因在于它本质上是宏展开会隐藏一些作用域问题而且在嵌套遍历同一个容器时容易发生迭代器状态错乱。新项目建议直接用范围for老项目能用范围for替换的也尽量替换。4.4 遍历顺序问题哈希表真的无序很多人刚开始用QHash时都会有一个疑问我明明先插入了apple再插入banana但遍历出来的顺序怎么是乱的答案是QHash的遍历顺序完全由内部哈希表决定不按插入顺序、不按键的字典序、不按任何你能逻辑推断的规则。这跟QMap完全不同QMap按键排序遍历结果永远是固定的升序。这个特性带来的影响很大。如果你依赖遍历顺序做某些逻辑比如把哈希表内容展示到界面上用户看到顺序每次启动都不一样那就必须放弃QHash改用QMap或者干脆用QVector存一堆Pair。另外同一个QHash在不同版本的Qt甚至不同编译选项下遍历顺序都可能不同因为内部桶的数量和哈希算法可能会有调整。提示遍历顺序不可预测是哈希表的本质特征不是bug。工程上如果对顺序有要求请在选择容器时就想清楚不要在代码里做顺序依赖。5. 删除操作看着简单坑其实不少5.1 remove、take、clear的区别QHash的删除操作主要有三个接口remove(key)、take(key)和clear()。它们的区别主要体现在两个维度返回值是什么、以及是否触发了值对象的析构或拷贝。remove(key)删除指定键及其值返回被删除的条目数量。对于QHash这种键唯一的容器返回值只能是0或1。看代码QHashQString, int hash; hash.insert(apple, 3); int removed hash.remove(apple); // removed 1键存在则删除 int removed2 hash.remove(pear); // removed2 0键不存在take(key)比remove多一个功能它会返回被删除的值。这个接口在你想删除键的同时拿到它的值、但不想再多做一次查找时非常方便QHashQString, int hash; hash.insert(apple, 3); int value hash.take(apple); // value 3且apple已从哈希表中移除clear()就不用多说了清空整个哈希表把所有键值对删除并释放内部桶内存。删除后你可以重新往这个容器里插数据容器仍然可用。一个容易忽略的性能细节是remove和take返回被删键值时如果值类型是QString这样带资源的对象删除操作会触发它的析构如果是take则是赋值或移动构造。虽然这些开销通常很小但在循环里删除几万条数据时也值得关注。更好的做法是先遍历收集要删除的键再统一删除或者直接用下面的迭代中删除方法。5.2 遍历过程中安全删除的标准姿势遍历时删除是最容易出问题的场景。很多人第一次遇到迭代器失效问题就是在遍历QHash时调用remove(key)然后程序崩溃或者行为诡异。原因很简单当你删除一个元素时迭代器内部的状态可能变得无效继续使用它就是未定义行为。在Qt中Java风格迭代器提供了安全的remove()方法STL风格迭代器则需要你把erase()的返回值赋回给当前迭代器。两种方式都有人用我分别给出推荐代码方法一Java风格迭代器遍历中直接删除QHashQString, int hash; // 假设hash里已经有很多数据 QHashIteratorQString, int it(hash); while (it.hasNext()) { it.next(); if (it.value() 0) { it.remove(); // 安全地从哈希表中移除当前条目 } }方法二STL风格迭代器利用erase返回值QHashQString, int::iterator it hash.begin(); while (it ! hash.end()) { if (it.value() 0) { it hash.erase(it); // erase会返回指向下一个元素的有效迭代器 } else { it; } }特别注意如果用的是它hash.erase(it)这种写法删完之后it已经指向了下一个元素所以不能再额外做it否则会跳过一个元素。代码里的else { it; }分支只在不删除时执行移动这是很多人容易写错的细节。另外我见过一种写法是遍历时先收集要删的key遍历结束后再统一删除QListQString keysToRemove; QHashQString, int::const_iterator it hash.constBegin(); for (; it ! hash.constEnd(); it) { if (it.value() 0) { keysToRemove.append(it.key()); } } for (const QString key : keysToRemove) { hash.remove(key); }这种方式优点是遍历过程完全只读没有任何迭代器失效风险逻辑也容易理解。缺点是多了两次遍历和一份键列表的临时内存。说实话在数据量不大几千条以内时这个方法是所有方案里最不容易出错的适合给团队里经验不太足的同学写。最后特别提醒一个隐藏点QHash的迭代器在有插入或删除行为发生时除了被删除对象自身的迭代器外其他元素的迭代器在Qt实现里通常不会失效这一点比标准库的unordered_map更友好但这不是官方标准保证依赖它是危险的。我的原则是永远按“可能失效”的假设来写删除逻辑稳妥永远是第一位的。6. 自定义类型的qHash实现与避坑6.1 为什么必须实现qHash和operatorC的整型、字符串这些内置类型Qt都帮你实现了哈希函数但如果你要拿自定义结构体当QHash的键就必须自己实现两个东西哈希函数qHash()和相等运算符operator。为什么缺一不可因为QHash内部流程是插入时用qHash(key)算桶位置查找时也先算哈希值定位到桶然后在桶内用operator逐个比较确认是不是同一个键。没有operator即使哈希值相同也没办法确定两个键真的相等没有qHash编译器根本不知道如何计算键的哈希值。看一个最简单的自定义结构体struct Person { QString name; int age; bool operator(const Person other) const { return name other.name age other.age; } }; // 需要全局函数或友元函数形式的qHash uint qHash(const Person key, uint seed) { return qHash(key.name, seed) ^ qHash(key.age, seed); }注意几个细节qHash的第二个参数seed在Qt 5.x以后引入了用于降低哈希碰撞攻击风险所以自定义实现时建议把它透传给内部的qHash调用最常用的组合方式是“不同字段哈希值按位异或”简单且能覆盖大多数场景。6.2 一个完整的自定义结构体使用示例下面是我实际项目里用过的一个写法场景是按“年级班级”把学生成绩分组统计struct ClassKey { QString grade; // 比如高一 int classNum 0; // 比如3代表三班 // 必须实现相等比较 bool operator(const ClassKey other) const { return grade other.grade classNum other.classNum; } }; // 全局qHash重载注意参数顺序 uint qHash(const ClassKey key, uint seed 0) { return qHash(key.grade, seed) ^ qHash(key.classNum, seed); } // 使用示例 QHashClassKey, int scoreSum; scoreSum.insert({高一, 3}, 280); scoreSum.insert({高二, 1}, 265); ClassKey key {高一, 3}; int sum scoreSum.value(key, 0); // sum 280这里有个很多新手会犯的错在qHash里忘了透传seed直接写return qHash(key.grade) ^ qHash(key.classNum);。这在旧版Qt4.x及更早是能编译的但在新版Qt里会因为匹配不到带seed的重载而报错或者更隐蔽地在启用哈希随机化后产生行为差异。所以无论用不用得上我都建议把seed透传进去这是兼容性最好的写法。另一个常犯的错是只实现了operator却没实现qHash编译直接报错no matching function for call to qHash(const Person, uint)。反过来只实现qHash但忘了operator编译大概率不报错但程序运行时会随机出现“找不到键”的诡异问题调试起来非常费劲。这一点值得依赖编译器又依赖直觉的开发者反复警惕。7. 常见问题与排查技巧实录7.1 QHash迭代器为什么突然失效迭代器失效是使用QHash时最高频的崩溃源头。我复盘过的几十个相关bug绝大多数是下面三种情况第一种遍历过程中直接调用hash.remove(key)。如前文所说这会让当前迭代器处于不确定状态再迭代下去就是未定义行为表现可能是崩溃、死循环、跳元素等。第二种遍历过程中往哈希表插入新元素。插入可能导致底层桶扩容扩容后所有迭代器都可能失效。即使这次没崩也不能保证下次不崩。第三种保存了一个迭代器在另一个作用域或另一个函数里修改了哈希表然后又回来用这个迭代器。这属于生命周期管理问题本质上跟第一、二种是同一类错误——迭代器只在对容器执行修改操作之前有效。排查这类问题我有一个非常实用的经验开启Qt的调试模式下作者容器包括QHash会在检测到迭代器使用不当或容器状态异常时输出警告信息包括qWarning和调试断言。在开发调试阶段把Qt代码的警告级别调到最高基本能第一时间定位问题。生产环境因为QT_NO_DEBUG宏禁用了断言所以很多问题只在debug模式暴露上了release就“神隐”但bug依然存在极度危险。7.2 隐式共享为什么你的副本“不复制”Qt容器QHash、QList、QVector等默认采用隐式共享也就是写时复制Copy-on-Write。两个QHash对象从同一个源头复制出来时底层共享同一份数据只有某个对象被修改时才会真正复制一份独立数据。这个特性对性能友好但容易带来语义上的混淆QHashQString, int hash; hash.insert(apple, 3); QHashQString, int copy hash; // 此时copy和hash共享底层数据 copy.insert(banana, 5); // 修改copy时copy自动复制一份不影响hash qDebug() hash.contains(banana); // falsehash不受影响看到上面的结果你可能会松一口气好在Qt自动处理了分离两个对象互不影响。但如果持有的是迭代器情况就变了如果你用一个非const迭代器遍历copy而这个迭代器绑定的是共享数据那么对它赋值或修改时Qt可能会自动触发分离导致你遍历的实际数据跟你以为的对象不是同一份。这种“隐式分离”行为在并发和父子类场景下尤其隐蔽。我的建议是如果要在多线程或者复杂生命周期里共享哈希表不要依赖隐式共享的自动分离语义显式调用detach()强制分离或者干脆用QSharedDataPointer做封装。自己控制复制时机避免让编译器替你做决定。7.3 调试QHash的几个实用小技巧调试哈希表比调试线性容器麻烦因为你看不到“下一个元素”的物理位置但只要掌握几个技巧效率能提升不少。第一用qDebug()直接输出整个哈希表内容不需要自己写遍历循环QHashQString, int hash; hash.insert(apple, 3); hash.insert(banana, 5); qDebug() hash; // 输出类似 QHash((apple, 3), (banana, 5))这个方法在需要快速确认某个函数执行后哈希表是否符合预期时非常好用省去了临时写循环的功夫。第二善用count()、size()、keys().contains()快速定位问题。比如怀疑某个键是否被误插入时直接打hash.contains(expectedKey)怀疑值被覆盖时打hash.keys(key)看哪个键映射到了这个值。第三遇到莫名其妙的值不对时先怀疑“读操作写入了默认值”的坑。如果你全程用的是operator[]取值检查一下有没有在只读场景误用了它。这是我在代码审查时最常给新人指出来的问题。8. 再聊几句关于QHash vs QMap的选型心得虽然标题是QHash但实际开发中免不了要跟QMap做对比。很多人写代码时根本不考虑容器特性随手用了一个等数据规模上来了、性能不达标了才回来重构。我建议把选型逻辑当成必须养成的习惯从需求出发定容器。核心判断依据很简单如果你需要“按键有序遍历”直接用QMap。红黑树的特性保证每次插入后元素按键排序遍历结果稳定有序。虽然查找和插入是O(log n)比QHash的O(1)慢一些但大多数业务场景下这个差距无感。而如果数据量大、只需要按键快速查找和修改对遍历顺序没要求QHash是更优选择。还有一个容易忽略的差异点内存占用。QHash为了减少碰撞桶的数量通常略大于元素个数也就是说存在一定冗余QMap则每个元素一个节点结构紧凑但每个节点需要存储左右子节点指针本身也有额外开销。实际项目中数据量在几万级别以下时两者在内存和性能上的差异都不大随便选都不会出大问题只有数据量到百万级甚至更高QHash的性能优势才会体现得比较明显。另外我还想分享一个在项目里的实践如果频繁做“按范围查找”或“取最大/最小值”这类操作QMap天然适合如果频繁做“按某个业务主键快速检索记录”QHash更适合。不同业务模块完全可以混用两种容器没必要在一个项目里统一成同一种。灵活组合往往比强行统一更合理。经验补充和QHash相关的几个容易被忽略的API不少人在用QHash时只盯着插入、取值、遍历、删除四个基础操作容易忽略一些看起来很不起眼、但实际能省很多事的接口。比如key(value)反向查找我之前提过它可能比较慢但如果数据量不大它比手写循环优雅得多。再比如keys()和values()两个方法能一次性取出所有键或所有值。keys()适合配合QList做批量操作values()适合在需要把值数组传给其他函数时用。还有s value(key, defaultValue)这个带默认值的版本在配置读取场景非常实用。比如从哈希表读取配置项如果键缺失就使用默认值代码可以写得很干净int timeout config.value(timeout, 5000); int retries config.value(retries, 3);contains()、isEmpty()这类简单判断也是日常高频使用的。比较容易被忽略的是uniqueKeys()和unite()它们专门针对多值场景。unite()相当于insertMulti的批量版本uniqueKeys()则返回所有不重复的键去掉重复键。如果项目里用了insertMulti这两个接口就是绝配。最后强调一下reserve()的兄弟方法squeeze()。如果你删除了大量数据后想释放底层多余内存可以调用squeeze()收缩容量。注意clear()之后内存也不一定完全释放所以“清空并归还内存”这个需求在Qt里要用两步表达hash.clear(); hash.squeeze();或者直接赋一个空的QHash对象。我写过一次内存峰值优化就是靠这些看起来不起眼的接口解决的。
返回列表