目录
基础类
Qt: QHash类的使用
QT QHash 使用方法实例
在QT中使用哈希表存储数据
QT之QHash简介
哈希表在QT中的应用举例
Qt扫盲-QHash理论总结
进阶类
深入剖析 Qt QHash :原理、应用与技巧
----------------------------------------------------
参考:
QHash,QMap,QSet与QList(QStringList)浅析 https://www.jianshu.com/p/984cf5017dce
QHash 与 QMap的区别_qhash和qmap的区别_马斯尔果的博客-CSDN博客
Qt扫盲-QMultiHash理论总结_qmultihash遍历_太阳风暴的博客-CSDN博客
深入剖析 Qt QMultiHash:原理、应用与技巧_泡沫o0的博客-CSDN博客
深入剖析 Qt QHash :原理、应用与技巧 http://www.cbww.cn/news/44566.shtml
Qhash简介_qhash排序_无畏烧风的博客-CSDN博客
================================
QHash是Qt提供的一个模板类,它可以实现一个基于哈希表的字典。哈希表是一种非常高效的数据结构,它可以在常数时间内完成插入、查找、删除等操作。¹
要使用QHash,你需要包含
#include
QHash
qhash;
要往QHash中插入一个键值对,可以使用[]运算符或者insert()函数。比如:
qhash["Alice"] = 20; // 使用[]运算符
qhash.insert("Bob", 25); // 使用insert()函数
要从QHash中获取一个键对应的值,可以使用value()函数或者[]运算符。比如:
int age1 = qhash.value("Alice"); // 使用value()函数
int age2 = qhash["Bob"]; // 使用[]运算符
要判断QHash中是否包含某个键,可以使用contains()函数。比如:
bool hasAlice = qhash.contains("Alice"); // 返回true
bool hasCindy = qhash.contains("Cindy"); // 返回false
要遍历QHash中存储的所有键值对,可以使用迭代器或者范围for循环。比如:
// 使用迭代器
QHash
while (it != qhash.constEnd()) {
qDebug() << it.key() << ":" << it.value();
++it;
}
// 使用范围for循环
for (auto it = qhash.begin(); it != qhash.end(); ++it) {
qDebug() << it.key() << ":" << it.value();
}
要从QHash中删除某个键值对,你可以使用erase()函数或者remove()函数。比如:
// 使用erase()函数,参数是迭代器
auto it = qhash.find("Alice");
if (it != qhash.end()) {
qhash.erase(it);
}
// 使用remove()函数,参数是键
qhash.remove("Bob");
————————————————
版权声明:本文为CSDN博主「weixin_48166125」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_48166125/article/details/131593995
QHash
#include
QHash
map;
map.insert("3name", "leo");
map.insert("1age", "18");
map.insert("2like", "eat");
map.insert("4sex", "man");
QHash
::iterator i;
for( i=map.begin(); i!=map.end(); ++i)
qDebug() << i.key() <<" " << i.value();
//生成一张哈希表,遍历时候怎么添加就怎么展示
qDebug() << "---------------------------------";
QHash::iterator mi;
mi = map.find("2like");
if(mi != map.end())
{
qDebug() << mi.key() <<" -- " << mi.value();
++mi;
if(mi != map.end())
{
qDebug() << mi.key() <<" ++ " << mi.value();
}
}
————————————————
版权声明:本文为CSDN博主「三月桃花浪」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/leoeitail/article/details/74684727
在QT中使用哈希表存储数据
QHash
例如:
定义一个哈希表
QHash
hash_IdToName;
将一个个键值对插入哈希表中
g_hashIdToName.insert(UserID,UserName);
定义一个迭代器
QHashIterator
iterator_IdAndName(g_hashIdToName); while(iterator_IdAndName.hasNext()) //若迭代器没有到达列表最后,返回true
{
iterator_IdAndName.next(); //返回下一个项目,迭代器前移一位
QString id = iterator_IdAndName.key(); //获取哈希表中的键
QString name = iterator_IdAndName.value(); //获取哈希表中的值
}
————————————————
版权声明:本文为CSDN博主「zhxg576」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhxg576/article/details/49124889
QHash
不同之处在于:
(1)QHash提供比QMap更快的查找,但所需空间更大。
(2)QMap默认按照键值升序排序;。使用QHash按照键值任意排序。
(3)QMap的键类型必须提供operator <()。QHash的键类型必须提供operator ==()和称为qHash()的全局哈希函数。
如果使用QHash中已存在的键调用insert(),则先前的值将被删除,如:
hash.insert("plenty", 100);
hash.insert("plenty", 2000);
// hash.value("plenty") == 2000
3,如果只需要从哈希中提取值(而不是键),则也可以使用foreach:
QHash
hash;
...
foreach (int value, hash)
cout << value << Qt::endl;
Qt进入foreach循环时会自动获取容器的副本,如果在迭代时修改容器,则不会影响循环(如果不修改容器,则仍会进行复制,但是由于隐式共享,复制容器非常快)。由于foreach会创建容器的副本,因此对变量使用非常量引用将不允许您修改原始容器,它只会影响副本。
Qt foreach循环的替代方法for是C ++ 11和更高版本中的基于范围的。但是,基于范围的for可能会强制Qt容器分离,而foreach不会。使用foreachAlways复制容器,对于STL容器通常并不容易。建议使用foreach Qt容器,并使用基于范围的forSTL 容器。
QHash的键类型除了是可分配的数据类型外,还具有其他要求:它必须提供operator ==(),并且该类型的名称空间中还必须有一个qHash()函数,该函数返回键类型的参数的哈希值。该qHash()函数计算基于密钥的数值。只要给定相同的参数,它总是返回相同的值,它就可以使用任何可以想象的算法。换句话说,如果e1 == e2,则qHash(e1) == qHash(e2)也必须成立。但是,为了获得良好的性能,qHash()函数应尝试最大程度地为不同的键返回不同的哈希值。例如:
#ifndef EMPLOYEE_H
#define EMPLOYEE_H
class Employee
{
public:
Employee() {}
Employee(const QString &name, QDate dateOfBirth);
...
private:
QString myName;
QDate myDateOfBirth;
};
inline bool operator==(const Employee &e1, const Employee &e2)
{
return e1.name() == e2.name()
&& e1.dateOfBirth() == e2.dateOfBirth();
}
inline uint qHash(const Employee &key, uint seed)
{
return qHash(key.name(), seed) ^ key.dateOfBirth().day();
}
#endif // EMPLOYEE_H
注意:如果是自定义类型或是第三方库类型作为哈希表的键值时,对perator ==()和qHash()函数的定义必须是全局的,要定义在使用QHash
error C2665: “qHash”: 25 个重载中没有一个可以转换所有参数类型
Q_CORE_EXPORT Q_DECL_PURE_FUNCTION uint qHashBits(const void *p, size_t size, uint seed = 0) Q_DECL_NOTHROW;
Q_DECL_CONST_FUNCTION Q_DECL_CONSTEXPR inline uint qHash(char key, uint seed = 0) Q_DECL_NOTHROW { return uint(key) ^ seed; }
Q_DECL_CONST_FUNCTION Q_DECL_CONSTEXPR inline uint qHash(uchar key, uint seed = 0) Q_DECL_NOTHROW { return uint(key) ^ seed; }
Q_DECL_CONST_FUNCTION Q_DECL_CONSTEXPR inline uint qHash(signed char key, uint seed = 0) Q_DECL_NOTHROW { return uint(key) ^ seed; }
Q_DECL_CONST_FUNCTION Q_DECL_CONSTEXPR inline uint qHash(ushort key, uint seed = 0) Q_DECL_NOTHROW { return uint(key) ^ seed; }
Q_DECL_CONST_FUNCTION Q_DECL_CONSTEXPR inline uint qHash(short key, uint seed = 0) Q_DECL_NOTHROW { return uint(key) ^ seed; }
Q_DECL_CONST_FUNCTION Q_DECL_CONSTEXPR inline uint qHash(uint key, uint seed = 0) Q_DECL_NOTHROW { return key ^ seed; }
Q_DECL_CONST_FUNCTION Q_DECL_CONSTEXPR inline uint qHash(int key, uint seed = 0) Q_DECL_NOTHROW { return uint(key) ^ seed; }
Q_DECL_CONST_FUNCTION Q_DECL_CONSTEXPR inline uint qHash(ulong key, uint seed = 0) Q_DECL_NOTHROW{return (sizeof(ulong) > sizeof(uint))? (uint(((key >> (8 * sizeof(uint) - 1)) ^ key) & (~0U)) ^ seed) : (uint(key & (~0U)) ^ seed);}
Q_DECL_CONST_FUNCTION Q_DECL_CONSTEXPR inline uint qHash(long key, uint seed = 0) Q_DECL_NOTHROW { return qHash(ulong(key), seed); }
Q_DECL_CONST_FUNCTION Q_DECL_CONSTEXPR inline uint qHash(quint64 key, uint seed = 0) Q_DECL_NOTHROW{ return uint(((key >> (8 * sizeof(uint) - 1)) ^ key) & (~0U)) ^ seed;}
Q_DECL_CONST_FUNCTION Q_DECL_CONSTEXPR inline uint qHash(qint64 key, uint seed = 0) Q_DECL_NOTHROW { return qHash(quint64(key), seed); }
Q_CORE_EXPORT Q_DECL_CONST_FUNCTION uint qHash(float key, uint seed = 0) Q_DECL_NOTHROW;
Q_CORE_EXPORT Q_DECL_CONST_FUNCTION uint qHash(double key, uint seed = 0) Q_DECL_NOTHROW;
#ifndef Q_OS_DARWIN
Q_CORE_EXPORT Q_DECL_CONST_FUNCTION uint qHash(long double key, uint seed = 0) Q_DECL_NOTHROW;
#endif
Q_DECL_CONST_FUNCTION Q_DECL_CONSTEXPR inline uint qHash(const QChar key, uint seed = 0) Q_DECL_NOTHROW { return qHash(key.unicode(), seed); }
Q_CORE_EXPORT Q_DECL_PURE_FUNCTION uint qHash(const QByteArray &key, uint seed = 0) Q_DECL_NOTHROW;
Q_CORE_EXPORT Q_DECL_PURE_FUNCTION uint qHash(const QString &key, uint seed = 0) Q_DECL_NOTHROW;
Q_CORE_EXPORT Q_DECL_PURE_FUNCTION uint qHash(const QStringRef &key, uint seed = 0) Q_DECL_NOTHROW;
Q_CORE_EXPORT Q_DECL_PURE_FUNCTION uint qHash(const QBitArray &key, uint seed = 0) Q_DECL_NOTHROW;
Q_CORE_EXPORT Q_DECL_PURE_FUNCTION uint qHash(QLatin1String key, uint seed = 0) Q_DECL_NOTHROW;
Q_CORE_EXPORT Q_DECL_PURE_FUNCTION uint qt_hash(const QString &key) Q_DECL_NOTHROW;
Q_CORE_EXPORT Q_DECL_PURE_FUNCTION uint qt_hash(const QStringRef &key) Q_DECL_NOTHROW;
templateinline uint qHash(const T *key, uint seed = 0) Q_DECL_NOTHROW
{
return qHash(reinterpret_cast(key), seed);
}
templateinline uint qHash(const T &t, uint seed)
Q_DECL_NOEXCEPT_EXPR(noexcept(qHash(t)))
{ return (qHash(t) ^ seed); }
templateinline uint qHash(const QPair &key, uint seed = 0)
Q_DECL_NOEXCEPT_EXPR(noexcept(qHash(key.first, seed)) && noexcept(qHash(key.second, seed)))
{
uint h1 = qHash(key.first, seed);
uint h2 = qHash(key.second, seed);
return ((h1 << 16) | (h1 >> 16)) ^ h2 ^ seed;
}
qHash中关于处理指针键值的哈希函数,如下:
Q_CORE_EXPORT Q_DECL_PURE_FUNCTION uint qHashBits(const void *p, size_t size, uint seed = 0) Q_DECL_NOTHROW;
template
inline uint qHash(const T *key, uint seed = 0) Q_DECL_NOTHROW
{
return qHash(reinterpret_cast(key), seed);
}指针哈希函数应用如下:(其中handle为第三方库智能指针,1、3例指针转int可能会溢出)
inline uint qHash(const Handle(AIS_Shape)& key)
{
return qHash(reinterpret_cast(key.get()));
//return qHashBits(key.get(), sizeof(AIS_Shape*), 0);
//return qHash((int)key.get());
}
使用QVector,QHash和QSet,附加项的性能摊销为O(log n)。在插入项目之前,可以通过调用QVector :: reserve(),QHash :: reserve()(或QSet :: reserve()并使用预期的项目数将其降至O(1)。
————————————————
版权声明:本文为CSDN博主「倚忆易逸」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_43405330/article/details/108028980
哈希表,是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。
QHash是QT类库中的一个容器类,内部维护了一张哈希表。QHash的内部哈希表每次翻倍增长,同时所有的内部元素都重新分配到桶内。
计算公式为
qHash(key)%QHash::capacity() (桶的个数)。
下面通过一个例子,来说明QHash类的使用。
这个例子中的主窗口,类似QQ的好友列表,双击一个好友,弹出聊天对话框,标题显示chat with +列表框中的内容。多次双击同一个好友,应该只有第一次弹对话框。
代码为:
QString id = ui->listWidget->currentItem()->text();
ChatDialog *dialog = new ChatDialog(id, this);
dialog->show();
当多次双击同一个列表项时,如下图所示,同样的窗口弹出了多个,逻辑上不合理。
为了解决这个问题,需要用到哈希表,用到QHash类。
实现过程如下:
1) 首先,需要在界面类中,引入头文件.
#include
2) 在类中加入一个私有成员:
QHash
chatFormHash;
3) 做信号和槽的关联
QObject::connect(ui->listWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)),this, SLOT(newWindow()));
4) 实现newWindow()函数
QString id = ui->listWidget->currentItem()->text();
ChatDialog *dialog;
if (this->chatFormHash.contains(id))
{
dialog = chatFormHash.value(id);
}
else
{
dialog = new ChatDialog(id, this);
this->chatFormHash.insert(id, dialog);
}
dialog->show();
列表框中的列表项字符串,作为哈希表中的key值,聊天窗口的地址做为哈希表的value.当哈希表中没有某个key时,创建一个新窗口。当第二次,双击同一个列表项时,直接从哈希表中查找窗口的地址即可,不需要再次创建,就解决了上面的问题。界面如下:
article/2023/4/20 12:21:56
在当今快速发展的技术世界中,高效的数据结构和算法变得越来越重要。它们是实现优秀软件性能和可扩展性的基石。在众多数据结构中,哈希表在各种应用场景中都发挥着重要作用。本博客将重点介绍QHash,一种高效且易用的哈希表实现,分享它的原理、特点以及在实际项目中的应用经验。
QHash是Qt库中的一种关联容器,它以键值对(key-value pairs)的形式存储数据。QHash的核心优势在于它的查找、插入和删除操作的平均时间复杂度为O(1),使得在大量数据处理场景下表现出优越的性能。通过使用哈希函数将键映射到值,QHash能够快速定位存储和检索的数据。
在接下来的文章中,我们将深入探讨QHash的内部工作原理,介绍它是如何实现快速查找和插入操作的。我们还将比较QHash与其他数据结构(如 QMap 和 std::unordered_map)的优缺点,以帮助读者了解何时选择QHash作为首选的数据结构。此外,我们还将分享一些实际应用案例,展示QHash在各种场景下的实际应用效果。敬请期待!
QHash 是 Qt 框架的一个关联容器类,它实现了一个哈希表,用于存储键值对。以下是按功能分类的 QHash 类的主要接口:
QHash 是 Qt 框架中的一个高效哈希表容器,用于存储键值对。在高级用法中,QHash 提供了一些强大的算法和功能来实现更复杂的需求。
QMap map;
map.insert("apple", 1);
map.insert("banana", 2);
map.insertMulti("apple", 3);
uint qHash(const CustomType &key, uint seed = 0) noexcept;
operator==
进行键值比较。如果你需要使用自定义比较函数,可以使用 QHash 的一个变体——QHashF,它接受一个自定义的比较函数对象。 QHash hash;
reserve()
函数预分配空间可以避免不必要的重新哈希。此外,capacity()
函数可以获取当前的容量,squeeze()
函数可以收缩容量以适应当前的元素数量。 hash.reserve(100);
int cap = hash.capacity();
hash.squeeze();
QMultiHash multiHash;
multiHash.insert("key", 1);
multiHash.insert("key", 2);
QList values = multiHash.values("key");
for (const auto &key : hash.keys()) { ... }
for (const auto &value : hash.values()) { ... }
for (auto it = hash.begin(); it != hash.end(); ++it) { ... }
QHash hash1, hash2;
hash1.swap(hash2);
hash1.subtract(hash2);
hash1.unite(hash2);
以下是一个包含 QHash 接口用法的综合代码示例。这个示例将展示如何使用 QHash 存储和操作键值对。为了简洁起见,这里使用 int
作为键(Key)类型,使用 QString
作为值(Value)类型。注释将解释每个接口的功能。
#include
#include
#include // 自定义哈希函数,这里我们使用 Qt 提供的 qHash() 函数作为示例
uint qHash(const int &key, uint seed = 0) {return qHash(key, seed);
}// 自定义键相等操作,这里直接使用整数的相等操作
bool operator==(const int &key1, const int &key2) {return key1 == key2;
}int main() {// 1. 构造一个空的 QHashQHash hash;// 2. 插入键值对hash.insert(1, "one");hash.insert(2, "two");hash.insert(3, "three");// 3. 访问元素qDebug() << "Value for key 1:" << hash[1]; // 输出 "one"// 4. 修改元素hash[1] = "ONE";qDebug() << "Modified value for key 1:" << hash[1]; // 输出 "ONE"// 5. 检查元素是否存在if (hash.contains(3)) {qDebug() << "Key 3 is in the hash.";}// 6. 删除元素hash.remove(2);// 7. 获取键和值列表QList keys = hash.keys();QList values = hash.values();// 8. 迭代 QHashQHash::iterator it;for (it = hash.begin(); it != hash.end(); ++it) {qDebug() << "Key:" << it.key() << "Value:" << it.value();}// 9. 从 QHash 提取一个值并删除该键值对QString value = hash.take(3);qDebug() << "Taken value for key 3:" << value; // 输出 "three"// 10. 检查 QHash 是否为空if (hash.isEmpty()) {qDebug() << "Hash is empty.";}// 11. 获取 QHash 的大小int size = hash.size();qDebug() << "Hash size:" << size; // 输出 2// 12. 清空 QHashhash.clear();if (hash.isEmpty()) {qDebug() << "Hash is now empty.";}// 13. 隐式共享QHash hash2;hash2 = hash; // hash 和 hash2 现在共享同一份数据if (hash.isSharedWith(hash2)) {qDebug() << "hash and hash2 are shared.";}return 0;
}
QHash 是 Qt 库中的一个关联容器,类似于 C++ 标准库中的 std::unordered_map。QHash 用于存储键值对,通过键进行高效的查找、插入和删除操作。这里提供一些 QHash 的高级用法:
使用自定义类型作为键或值:
要使用自定义类型作为 QHash 的键或值,首先需要为该类型提供一个哈希函数和相等比较运算符。这是因为 QHash 依赖于哈希值来确定存储位置,且需要相等运算符进行键的比较。例如,定义一个名为 CustomType 的类型:
class CustomType {
public:int a;QString b;bool operator==(const CustomType &other) const {return a == other.a && b == other.b;}
};// 在名为 qHash 的自定义哈希函数中提供哈希值
uint qHash(const CustomType &key, uint seed = 0) {return qHash(key.a, seed) ^ qHash(key.b, seed);
}
现在可以在 QHash 中使用 CustomType 类型:
QHash customTypeHash;
使用 lambda 表达式自定义哈希和比较函数:
在 C++11 及其以后的版本中,可以使用 lambda 表达式替代自定义哈希函数和相等运算符。例如:
auto customHash = [](const CustomType &key) -> uint {return qHash(key.a) ^ qHash(key.b);
};auto customEqual = [](const CustomType &key1, const CustomType &key2) -> bool {return key1.a == key2.a && key1.b == key2.b;
};QHash customTypeHash(customHash, customEqual);
QHash 的合并和交集:
可以使用标准库算法来实现 QHash 的合并和交集。例如,合并两个 QHash:
QHash hash1, hash2, mergedHash;// 合并 hash1 和 hash2
mergedHash.reserve(hash1.size() + hash2.size());
mergedHash.unite(hash1);
mergedHash.unite(hash2);
计算两个 QHash 的交集:
QHash hash1, hash2, intersectionHash;for (const auto &key : hash1.keys()) {if (hash2.contains(key)) {intersectionHash.insert(key, hash1.value(key));}
}
QHash 的 value 初始化:
在 QHash 中插入新键时,可以通过 QHash::operator[]
和 QHash::insert
方法同时初始化对应的值。对于 operator[]
,如果键不存在,则会插入一个默认初始化的值。而 insert
则可以同时插入键和值。
QHash myHash;
myHash["key1"] = 42; // 使用 operator[] 插入键 "key1" 并初始化值为 42
myHash.insert("key2", 100); // 使用 insert() 插入键 "key2" 和值 100
QHash 的 value 更新:
通过 QHash::operator[] 可以更新已存在键的值。如果键不存在,会插入一个新的键值对。此外,也可以使用 QHash::insert 方法更新已存在键的值。
QHash myHash;
myHash["key1"] = 42;
myHash["key1"] = 100; // 使用 operator[] 更新 "key1" 的值为 100
myHash.insert("key1", 200); // 使用 insert() 更新 "key1" 的值为 200
QHash 的键值遍历:
使用范围 for 循环遍历 QHash 中的键值对:
QHash myHash;
// ... 添加一些键值对for (const auto &key : myHash.keys()) {int value = myHash.value(key);qDebug() << "Key:" << key << "Value:" << value;
}
或使用迭代器遍历:
QHash::const_iterator i;
for (i = myHash.constBegin(); i != myHash.constEnd(); ++i) {qDebug() << "Key:" << i.key() << "Value:" << i.value();
}
QHash 的条件查找:
可以使用 QHash::find
或 QHash::constFind
方法查找满足特定条件的键值对。例如,查找值大于 50 的键值对:
QHash myHash;
// ... 添加一些键值对QHash::const_iterator i;
for (i = myHash.constBegin(); i != myHash.constEnd(); ++i) {if (i.value() > 50) {qDebug() << "Key:" << i.key() << "Value:" << i.value();}
}
删除指定条件的键值对:
使用 QHash::erase
方法删除满足特定条件的键值对。例如,删除值小于 10 的键值对:
QHash myHash;
// ... 添加一些键值对QHash::iterator i = myHash.begin();
while (i != myHash.end()) {if (i.value() < 10) {i = myHash.erase(i);} else {++i;}
}
在Qt中,QHash是一个基于哈希表的关联容器,可以用来存储键值对。要遍历QHash中的元素,可以使用迭代器。QHash提供了两种迭代器:正向迭代器(QHash::iterator)和只读常量迭代器(QHash::const_iterator)。
以下是一个简单的示例,展示了如何使用迭代器遍历QHash中的元素:
#include
#include
#include
#include int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);// 创建一个QHash并插入键值对QHash hash;hash.insert("one", 1);hash.insert("two", 2);hash.insert("three", 3);// 使用正向迭代器遍历QHashqDebug() << "Using iterator:";QHash::iterator i;for (i = hash.begin(); i != hash.end(); ++i) {qDebug() << i.key() << ": " << i.value();}// 使用只读常量迭代器遍历QHashqDebug() << "Using const_iterator:";QHash::const_iterator ci;for (ci = hash.constBegin(); ci != hash.constEnd(); ++ci) {qDebug() << ci.key() << ": " << ci.value();}return a.exec();
}
在这个示例中,我们首先创建了一个QHash并插入了三个键值对。然后,我们使用正向迭代器遍历QHash中的元素。注意,迭代器允许修改元素的值,但不能修改键。接下来,我们使用只读常量迭代器遍历QHash。只读常量迭代器不允许修改元素。
遍历QHash中的元素时,请注意哈希表中元素的顺序是不确定的。因此,在遍历过程中,元素可能会以不同于插入顺序的顺序显示。如果需要有序的关联容器,请使用QMap。
QHash 是 Qt 容器类中的一个关联容器,主要用于存储键值对。与其他容器相比,QHash 有其特点和优势。这里我们将 QHash 与其他 Qt 容器类和 C++ 标准库容器进行对比。
QHash 和 QMap 都是 Qt 提供的关联容器,用于存储键值对。它们的主要区别在于底层实现和性能特点。
选择 QHash 还是 QMap 取决于具体应用场景。如果需要快速查找、插入和删除操作,且不关心键值对的顺序,可以选择 QHash。如果需要按键顺序存储键值对,或者对内存占用有较高要求,可以选择 QMap。
QHash 和 std::unordered_map 都是基于哈希表实现的关联容器,用于存储键值对。它们的主要区别在于 API 设计和内存管理策略。
在使用 Qt 应用开发时,如果需要与 Qt 的其他组件协同工作,QHash 可能是更好的选择。而在不涉及 Qt 组件的纯 C++ 项目中,可以考虑使用 std::unordered_map。
总之,选择合适的容器取决于具体的应用场景和性能需求。在实际项目中,可以根据需求进行测试和评估,以确定最适合的容器类型。
以下是一个简单的示例,演示了如何测量 QHash 和 std::unordered_map 的各种操作的耗时,并输出结果。请注意,这个示例不具备统计学意义,只是为了展示如何进行性能比较。在实际应用中,你需要针对你的具体需求进行更为全面和细致的性能评估。
#include
#include
#include
#include
#include int main() {const int elementCount = 100000;// Prepare random keys and valuesstd::vector keys(elementCount);std::vector values(elementCount);std::random_device rd;std::mt19937 gen(rd());std::uniform_int_distribution<> dis(1, elementCount * 10);for (int i = 0; i < elementCount; ++i) {keys[i] = dis(gen);values[i] = dis(gen);}// Measure QHash performanceQHash qhash;auto start = std::chrono::steady_clock::now();for (int i = 0; i < elementCount; ++i) {qhash[keys[i]] = values[i];}auto end = std::chrono::steady_clock::now();auto qhash_insert_duration = std::chrono::duration_cast(end - start).count();// Measure std::unordered_map performancestd::unordered_map unordered_map;start = std::chrono::steady_clock::now();for (int i = 0; i < elementCount; ++i) {unordered_map[keys[i]] = values[i];}end = std::chrono::steady_clock::now();auto unordered_map_insert_duration = std::chrono::duration_cast(end - start).count();// Print resultsstd::cout << "QHash insert duration: " << qhash_insert_duration << " microseconds" << std::endl;std::cout << "std::unordered_map insert duration: " << unordered_map_insert_duration << " microseconds" << std::endl;std::cout << "Time difference: " << std::abs(qhash_insert_duration - unordered_map_insert_duration) << " microseconds" << std::endl;return 0;
}
这个示例首先生成了一组随机的键和值,然后分别向 QHash 和 std::unordered_map 插入这些键值对,同时记录插入操作的耗时。最后,打印出两种关联容器插入操作的耗时及其差值。你可以根据需要扩展此示例,以测试其他操作(如查找、删除等)的性能。
要编译此示例,请确保已安装 Qt 库并配置了正确的编译环境。如果你使用的是 qmake,请在项目文件中添加 QT += core
以引入 Qt Core 模块。
QHash 是 Qt 框架中的一个关联容器类,用于存储键值对。它的底层数据结构是哈希表,通过哈希函数将键映射到桶(bucket)中。下面详细介绍 QHash 的底层原理和内存管理。
底层原理:
内存管理:
reserve()
函数预先分配足够的内存。这可以避免频繁的动态内存分配和释放,从而提高性能。预留容量只影响 QHash 的哈希表大小,不影响已存储的元素数量。当元素数量增长超过预留容量时,QHash 会自动进行扩容。综上所述,QHash 的底层原理基于哈希表,可以实现快速查找、插入和删除操作。内存管理方面,QHash 使用隐式共享技术来减少内存占用和拷贝开销,同时可以通过调整负载因子和预留容量来平衡性能与内存占用。对于自定义类型,内存管理取决于类型的实现,你可以通过提供相应的函数来实现更精细的内存管理控制。
QHash 是一个基于哈希表的关联容器,它的性能特点在于平均情况下快速的查找、插入和删除操作。以下是对 QHash 主要操作的性能分析:
总之,QHash 在平均情况下为查找、插入和删除操作提供了非常高的性能。当哈希冲突较少时,这些操作的时间复杂度接近 O(1)。为了保持良好的性能,QHash 根据负载因子自动调整大小,以减少哈希冲突的可能性。然而,实际应用中,性能可能会受到哈希函数、负载因子和数据分布的影响。
在使用 QHash 时,可能会遇到一些问题。以下是一些常见问题及其解决方案:
qHash()
函数和 operator==()
。确保哈希函数具有良好的分布特性,以减少哈希冲突的可能性。 class CustomKey {// ...
};inline bool operator==(const CustomKey &a, const CustomKey &b) {// 实现相等操作符
}inline uint qHash(const CustomKey &key, uint seed = 0) {// 实现哈希函数
}QHash customHash;
value()
函数时,如果键不存在,将返回一个默认构造的值。这可能导致意外的结果,尤其是在使用自定义数据类型作为值时。 解决方案:在调用 value()
函数之前,使用 contains()
函数检查键是否存在。这样可以避免意外的结果。另一种方法是使用 find()
函数,它返回一个迭代器,可以用来检查键是否存在。 QHash hash;
QString key = "some_key";if (hash.contains(key)) {ValueType value = hash.value(key);// 处理找到的值
} else {// 键不存在时的处理
}// 或使用迭代器
QHash::const_iterator it = hash.find(key);
if (it != hash.constEnd()) {ValueType value = it.value();// 处理找到的值
} else {// 键不存在时的处理
}
QHash hash;
// ... 填充 QHash ...// 获取所有键
QStringList keys = hash.keys();// 对键进行排序
keys.sort();// 按照排序后的键遍历 QHash
for (const QString &key : keys) {ValueType value = hash.value(key);// 处理键值对
}
QHash是Qt中一个高效的哈希表实现,用于存储键值对。以下是一些常见的应用场景:
总之,QHash是一个灵活且高效的关联容器,适用于各种应用场景。根据需要,您可以使用QHash来简化代码、提高性能以及实现更好的数据组织。
QHash是Qt中一个高效的关联容器,用于存储键值对。在实际项目中,QHash可以在多种场景下应用,以下是一些实际案例:
在实际项目中,我们经常需要缓存一些数据,以减少不必要的计算或网络请求。例如,假设我们正在开发一个获取用户个人资料的应用程序。为避免频繁发起网络请求,我们可以使用QHash来缓存已请求过的用户资料。
#include
#include
#include
#include "UserProfile.h"class UserProfileCache {
public:QSharedPointer getUserProfile(const QString &userId) {if (cache.contains(userId)) {return cache.value(userId);}QSharedPointer userProfile = fetchUserProfileFromServer(userId);cache.insert(userId, userProfile);return userProfile;}private:QHash> cache;QSharedPointer fetchUserProfileFromServer(const QString &userId) {// 发起网络请求,获取用户资料}
};
我们可以使用QHash作为计数器,例如在一个文本编辑器中统计单词出现的次数。
#include
#include
#include class DynamicObject {
public:void setProperty(const QString &name, const QVariant &value) {properties.insert(name, value);}QVariant property(const QString &name) const {return properties.value(name);}void removeProperty(const QString &name) {properties.remove(name);}private:QHash properties;
};
在某些情况下,我们可能需要为对象动态地添加或删除属性。这时,可以使用QHash将属性名作为键,属性值作为值。
#include
#include
#include class DynamicObject {
public:void setProperty(const QString &name, const QVariant &value) {properties.insert(name, value);}QVariant property(const QString &name) const {return properties.value(name);}void removeProperty(const QString &name) {properties.remove(name);}private:QHash properties;
};
QHash 本身不是线程安全的。在多个线程中访问或修改同一个 QHash 实例可能会导致不确定的行为或数据竞争。为了在多线程环境中使用 QHash,您需要采取一些措施来确保线程安全。
以下是一些在多线程环境中使用 QHash 的方法:
#include
#include
#include class ThreadSafeHash {
public:void insert(const QString &key, const QVariant &value) {QMutexLocker locker(&mutex);hash.insert(key, value);}QVariant value(const QString &key) const {QMutexLocker locker(&mutex);return hash.value(key);}private:QHash hash;mutable QMutex mutex;
};
#include
#include
#include
#include class ThreadSafeHash {
public:void insert(const QString &key, const QVariant &value) {QWriteLocker locker(&lock);hash.insert(key, value);}QVariant value(const QString &key) const {QReadLocker locker(&lock);return hash.value(key);}private:QHash hash;mutable QReadWriteLock lock;
};
请注意,在某些情况下,使用锁可能会导致性能下降。在这种情况下,您可能需要调查其他数据结构或并发技术,例如无锁编程、原子操作或线程局部存储。
从 Qt5 到 Qt6,QHash 主要经历了以下变化:
QHash::take()
函数,它可以安全地删除元素,而不会导致迭代器失效。qHash()
,因为现在可以直接使用 qHashRange()
函数。此外,还移除了 QHash::operator[]()
的 const 版本,因为它的行为可能导致错误。替代方法是使用 QHash::value()
函数来获取键对应的值。QHash::from()
函数。例如,在 Qt5 中可以使用如下代码: QHash hash = {{1, "one"},{2, "two"},{3, "three"}
};
在 Qt6 中,可以使用如下代码: QHash hash = QHash::from({{1, "one"},{2, "two"},{3, "three"}
});
在这个时间段内,QHash 没有显著的变化。
在 Qt5.14 到 Qt5.15 期间,QHash 经历了一个主要的 API 更新:
QHash::removeIf()
成员函数,这使得我们能基于特定条件从 QHash 中删除元素。Qt6.0 是一个主要的版本更新,主要关注性能、内存使用和源代码兼容性。QHash 在此版本中有以下变化:
QHash::QHash(InputIterator, InputIterator)
,允许从迭代器范围构造 QHash。QHash::multiFind()
成员函数,返回一个给定键的值范围的迭代器对。这有助于更高效地在 QHash 中查找多个值。QHash::contains()
和 QHash::isEmpty()
。在 Qt6.0 到 Qt6.1 之间,QHash 没有显著的变化。
在 Qt6.1 到 Qt6.2 之间,QHash 也没有显著的变化。
请注意,这里总结的变化截至 2021 年 9 月,随着 Qt 版本的持续更新,可能会有新的变化和优化。要获取最新信息,请参考 Qt 的官方文档。
亲爱的读者,感谢您陪伴我们一起深入了解 QHash 这个强大的数据结构。心理学告诉我们,学习新知识和技能对于保持我们大脑敏锐和适应不断变化的世界至关重要。事实上,学习是一种生存机制,有助于我们应对挑战并在社会中获得成功。
同样,QHash 的高效性和灵活性使我们在处理复杂问题时更加游刃有余,为我们提供了强大的工具来更好地理解和掌控我们周围的数字世界。就像心理学所强调的,我们的思维方式和行为模式对于我们的成长和发展有着深远的影响,而在技术领域,掌握 QHash 等高效工具能够为我们带来巨大的优势。
在此,我们邀请您将这篇博客收藏起来,以便在将来遇到相关问题时能迅速找到它。同时,如果您觉得这篇文章对您有所帮助,也请不吝点赞以示支持。这不仅会对作者产生积极的心理反馈,激励我们持续创作更多有价值的内容,同时也有助于更多的人找到这篇文章,共同探索知识的魅力。
最后,愿我们在不断学习的道路上共同进步,共创美好的未来。
QHash理论总结
一、概述
二、使用
1. 添加 元素
2. 获取元素
3. 遍历元素
4. 删除元素
5. qHash()的散列函数
6.算法复杂性
一、概述
QHash是Qt的通用容器类之一。它和QMap一样是用来存储(键,值)对的工具,并提供快速查找与键相关联的值的功能。
QHash提供了与QMap非常相似的功能。区别在于:
QHash提供了比QMap更快的查找。
在遍历QMap时,元素总是按键排序。在QHash时,元素在QHash内部的顺序是无序的。QMap内部是有序的
QMap的键类型必须含有< ()运算符。QHash的键类型必须能提供==()运算符和一个名为QHash()的全局散列函数(参见QHash)。其实就是构造函数要重载这些运算符,一般我们用的都是QString做键,所以不要慌。
二、使用
1. 添加 元素
下面是一个QHash的例子,键为QString,值为int:
QHash
hash;
要在散列中插入一个(键,值)对,可以使用运算符:
hash["one"] = 1;
hash["three"] = 3;
hash["seven"] = 7;
这会将以下3个(键,值)对插入到QHash中:(“one”,1), (“three”,3),和(“seven”,7)。
另一种向散列中插入元素的方法是使用insert():
hash.insert("twelve", 12);
要查找一个值,可以使用operator或value():
int num1 = hash["thirteen"];
int num2 = hash.value("thirteen");
如果散列中没有指定键的项,这些函数返回一个默认构造的值。int、double类返回 0,QString 返回空字符串。
2. 获取元素
如果你想检查散列值是否包含特定的键,可以使用contains():
int timeout = 30;
if (hash.contains("TIMEOUT"))
timeout = hash.value("TIMEOUT");
还有一个value()重载方法,如果指定的键不存在,则使用第二个参数作为默认值:
int timeout = hash.value("TIMEOUT", 30);
一般来说,我们推荐使用contains()和value()而不是 [ ] 在散列中查找键。
原因在于,如果没有键相同的元素存在,[ ]运算符会静默地将元素插入到散列中(除非散列值是const)。
例如,下面的代码片段将在内存中创建1000个元素:
// WRONG
QHashhash;
...
for (int i = 0; i < 1000; ++i) {
if (hash[i] == okButton)
cout << "Found button at index " << i << Qt::endl;
}
为了避免这个问题,请将上面代码中的hash[i]替换为hash.value(i)。
在内部,QHash使用散列表来执行查找。这个散列表会自动增长和缩小,以提供快速查找,而不会浪费太多内存。如果你已经知道QHash大约包含多少个元素,那么仍然可以通过调用reserve()来控制散列表的大小,但这对获得良好的性能来说不是必要的。你也可以调用capacity()来取得散列表的大小。
3. 遍历元素
如果想遍历存储在QHash中的所有键值对,可以使用迭代器。QHash提供了java风格的迭代器(QHashIterator和QMutableHashIterator)和stl风格的迭代器(QHash::const_iterator和QHash::iterator)。下面是如何使用java风格的迭代器迭代QHash
QHashIterator
i(hash);
while (i.hasNext()) {
i.next();
cout << i.key() << ": " << i.value() << Qt::endl;
}
下面是相同的代码,但使用了stl风格的迭代器:
QHash
::const_iterator i = hash.constBegin();
while (i != hash.constEnd()) {
cout << i.key() << ": " << i.value() << Qt::endl;
++i;
}
QHash是无序的,因此不能假定迭代器的序列是可预测的。如果需要按键排序,则使用QMap。
通常,QHash每个键只允许一个值。如果使用QHash中已经存在的键调用insert(),前一个值将被删除。例如:
hash.insert("plenty", 100);
hash.insert("plenty", 2000);
// hash.value("plenty") == 2000
然而,你可以使用insertMulti()而不是insert()来为每个键存储多个值(或者使用便捷的子类QMultiHash)。
如果想取得一个键对应的所有值,可以使用values(const key &key),它会返回一个QList:
QList
values = hash.values("plenty");
for (int i = 0; i < values.size(); ++i)
cout << values.at(i) << Qt::endl;
共享相同键的项获取的方法是调用find()来获取第一个键对应的元素的迭代器,然后从那里开始迭代:
QHash
::iterator i = hash.find("plenty");
while (i != hash.end() && i.key() == "plenty") {
cout << i.value() << Qt::endl;
++i;
}
如果你只需要从散列中提取值(而不是键),也可以使用foreach:
QHash
hash;
...
foreach (int value, hash)
cout << value << Qt::endl;
4. 删除元素
有几种方法可以从散列中删除元素。一种方法是调用remove()方法;这将删除具有给定键的任何项。另一种方法是使用QMutableHashIterator::remove()。此外,还可以使用clear()清除整个QHash表。
QHash的键和值数据类型必须是可分配的数据类型。例如,您不能将QWidget存储为值;相反,存储一个QWidget *。
qHash()散列函数
5. qHash()的散列函数
QHash内部使用哈希表来索引值的。这个是部分原理
除了是可赋值的数据类型外,QHash的键类型还有其他要求:它必须提供 == 运算符,并且在该类型的命名空间中还必须有一个qHash()函数,该函数返回键类型参数的散列值。
函数qHash()根据键计算数值。它可以使用任何可以想象到的算法,只要给定相同的参数时总是返回相同的值。换句话说,如果e1 == e2,那么qHash(e1) == qHash(e2)也必须成立。然而,为了获得良好的性能,qHash()函数应该尽可能地为不同的键返回不同的散列值。
对于密钥类型K, qHash函数必须具有以下签名之一:
uint qHash(K key);
uint qHash(const K &key);uint qHash(K key, uint seed);
uint qHash(const K &key, uint seed);
两个重载参数接受一个无符号整数,该整数将用于散列函数的计算。该种子由QHash提供,用于防止一系列算法复杂性攻击。如果key类型同时定义了单参数和双参数重载,QHash将使用后者(注意,你可以简单地定义一个双参数版本,并为seed参数使用默认值)。
以下是可以作为QHash键的c++和Qt类型的部分列表:任何整数类型(char、unsigned long等)、任何指针类型、QChar、QString和QByteArray。对于所有这些,头文件定义了一个QHash()函数,用于计算适当的散列值。许多其他Qt类也为它们的类型声明了一个qHash重载。请参阅每个类的文档。
如果想使用其他类型作为键,请确保提供==()运算符和qHash()实现。这个就是把 Employee 作为键的例子
例子:
#ifndef EMPLOYEE_H
#define EMPLOYEE_Hclass Employee
{
public:
Employee() {}
Employee(const QString &name, QDate dateOfBirth);
...private:
QString myName;
QDate myDateOfBirth;
};inline bool operator==(const Employee &e1, const Employee &e2)
{
return e1.name() == e2.name()
&& e1.dateOfBirth() == e2.dateOfBirth();
}inline uint qHash(const Employee &key, uint seed)
{
return qHash(key.name(), seed) ^ key.dateOfBirth().day();
}#endif // EMPLOYEE_H
在上面的例子中,我们依赖Qt的全局qHash(const QString &, uint)来为我们提供员工名字的哈希值,并将其与他们出生的日期异或,以帮助生成具有相同名字的人的唯一哈希值。
请注意,Qt提供的qHash()重载的实现可能随时改变。你不能指望qHash()在不同的Qt版本中(对于相同的输入)会得到相同的结果。
6.算法复杂性
所有的散列表都容易受到一类特定的拒绝服务攻击,攻击者会仔细地预先计算一组不同的键,这些键将被散列到散列表的同一个桶中(甚至具有相同的散列值)。攻击的目的是在数据被输入到表中时获得最坏情况下的算法行为(O(n),而不是平摊O(1),详情请参阅算法复杂性。这个就是哈希算法函数本身的问题。也就是那个散列函数把多个值计算到同一个位置了。
为了避免这种最坏情况下的行为,可以在qHash()计算哈希值时添加随机种子,从而消除攻击的范围。该种子由QHash在每个进程中自动生成一次,然后作为QHash()函数的两个参数重载的第二个参数传递给QHash。
QHash的这种随机化在默认情况下是启用的。尽管程序永远不应该依赖于特定的QHash排序,但在某些情况下,您可能需要临时确定的行为,例如调试或回归测试。要禁用随机化,可以定义环境变量QT_HASH_SEED的值为0。或者,你可以调用qSetGlobalQHashSeed()函数,参数值为0。
————————————————
版权声明:本文为CSDN博主「太阳风暴」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_43680827/article/details/128552403