Qt: QHash类的使用 汇总

目录

基础类

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博客

================================

Qt: QHash类的使用

        QHash是Qt提供的一个模板类,它可以实现一个基于哈希表的字典。哈希表是一种非常高效的数据结构,它可以在常数时间内完成插入、查找、删除等操作。¹

        要使用QHash,你需要包含头文件,并指定键和值的类型。比如,如果想用QString作为键,int作为值,可以这样定义一个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::const_iterator it = qhash.constBegin();
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

QT QHash 使用方法实例

QHash是一个在哈希表中存储键值对的数据结构。它的接口几乎与QMap相同,但是与QMap相比,它对K的模板类型有不同的要求,而且它提供了比QMap更快的查找功能。

添加头文件:

#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

QT之QHash简介

 QHash 是Qt的通用容器类之一。它存储(键,值)对,并提供与键关联的值的快速查找。

1,QHash提供与QMap非常相似的功能。

不同之处在于:

(1)QHash提供比QMap更快的查找,但所需空间更大。

(2)QMap默认按照键值升序排序;。使用QHash按照键值任意排序。

(3)QMap的键类型必须提供operator <()。QHash的键类型必须提供operator ==()和称为qHash()的全局哈希函数。

2,一个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 容器。

4,qHash()函数

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之前,如果有命名空间,则是定义在命名空间之外的,否则qt模板函数是无法通过自定义模板::类名::qHash来调用自定义的哈希函数,会出现如下错误:

 error C2665: “qHash”: 25 个重载中没有一个可以转换所有参数类型  

5,QHash实现的键值哈希函数

    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;
     
    template inline uint qHash(const T *key, uint seed = 0) Q_DECL_NOTHROW
    {
        return qHash(reinterpret_cast(key), seed);
    }
    template inline uint qHash(const T &t, uint seed)
        Q_DECL_NOEXCEPT_EXPR(noexcept(qHash(t)))
    { return (qHash(t) ^ seed); }
     
    template inline 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());
        }

6,Qt关联容器和集合的算法复杂性: Qt: QHash类的使用 汇总_第1张图片

使用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

哈希表在QT中的应用举例

哈希表,是根据关键码值(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();

当多次双击同一个列表项时,如下图所示,同样的窗口弹出了多个,逻辑上不合理。

Qt: QHash类的使用 汇总_第2张图片

为了解决这个问题,需要用到哈希表,用到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时,创建一个新窗口。当第二次,双击同一个列表项时,直接从哈希表中查找窗口的地址即可,不需要再次创建,就解决了上面的问题。界面如下:

Qt: QHash类的使用 汇总_第3张图片

深入剖析 Qt QHash :原理、应用与技巧

article/2023/4/20 12:21:56

目录标题

  • 引言
  • QHash 基础用法
    • 基础用法示例
    • 基础用法综合示例
  • QHash 的高级用法
  • 迭代器:遍历 QHash 中的元素(Iterators: Traversing Elements in QHash )
  • QHash和其他容器的对比
    • QHash 和 std::unordered\_map
  • QHash的底层原理和内存管理
  • QHash 的性能分析:查找、插入与删除操作
  • 使用QHash 可能遇到的问题和解决方案.
  • QHash 的应用场景
  • 实战案例:QHash 在实际项目中的应用(Practical Examples: QHash in Real-World Projects)
    • 案例1:缓存
    • 案例2:计数器
    • 案例3:对象属性
  • 线程安全性与 QHash 的并发使用(Thread Safety and Concurrent Usage of QHash )
  • QT各版本中QHash的变化
      • Qt5 到 Qt5.14
      • Qt5.14 到 Qt5.15
      • Qt5.15 到 Qt6.0
      • Qt6.0 到 Qt6.1
      • Qt6.1 到 Qt6.2
  • 结语

引言

在当今快速发展的技术世界中,高效的数据结构和算法变得越来越重要。它们是实现优秀软件性能和可扩展性的基石。在众多数据结构中,哈希表在各种应用场景中都发挥着重要作用。本博客将重点介绍QHash,一种高效且易用的哈希表实现,分享它的原理、特点以及在实际项目中的应用经验。

QHash是Qt库中的一种关联容器,它以键值对(key-value pairs)的形式存储数据。QHash的核心优势在于它的查找、插入和删除操作的平均时间复杂度为O(1),使得在大量数据处理场景下表现出优越的性能。通过使用哈希函数将键映射到值,QHash能够快速定位存储和检索的数据。

在接下来的文章中,我们将深入探讨QHash的内部工作原理,介绍它是如何实现快速查找和插入操作的。我们还将比较QHash与其他数据结构(如 QMap 和 std::unordered_map)的优缺点,以帮助读者了解何时选择QHash作为首选的数据结构。此外,我们还将分享一些实际应用案例,展示QHash在各种场景下的实际应用效果。敬请期待!

QHash 基础用法

QHash 是 Qt 框架的一个关联容器类,它实现了一个哈希表,用于存储键值对。以下是按功能分类的 QHash 类的主要接口:

  1. 构造与析构:
    • QHash()
    • QHash(const QHash &other)
    • ~QHash()
  2. 容量:
    • int count(const Key &key) const
    • bool isEmpty() const
    • int size() const
  3. 访问元素:
    • const T &operator[](const Key &key) const
    • T &operator[](const Key &key)
    • T value(const Key &key, const T &defaultValue = T()) const
  4. 修改元素:
    • void insert(const Key &key, const T &value)
    • T &insertMulti(const Key &key, const T &value)
    • void remove(const Key &key)
    • T take(const Key &key)
  5. 查找:
    • bool contains(const Key &key) const
    • int count(const Key &key) const
    • iterator find(const Key &key)
    • const_iterator find(const Key &key) const
    • iterator begin()
    • const_iterator begin() const
    • iterator end()
    • const_iterator end() const
  6. 键和值操作:
    • QList keys() const
    • QList keys(const T &value) const
    • QList values() const
    • QList values(const Key &key) const
  7. 比较:
    • bool operator==(const QHash &other) const
    • bool operator!=(const QHash &other) const
  8. 交换:
    • void swap(QHash &other)
  9. 散列函数和负载因子:
    • int capacity() const
    • void clear()
    • int reserve(int size)
    • float load_factor() const
    • float max_load_factor() const
    • void max_load_factor(float z)
  10. 其他:
  • void detach()
  • bool isDetached() const
  • uint qHash(const Key &key, uint seed = 0)

基础用法示例

QHash 是 Qt 框架中的一个高效哈希表容器,用于存储键值对。在高级用法中,QHash 提供了一些强大的算法和功能来实现更复杂的需求。

  1. 插入: QMap 支持多种插入方法,例如:
    • insert():插入一个键值对,如果已存在相同的键,则值将被替换。
    • insertMulti():插入一个键值对,即使存在相同的键,值也会被保留。
    QMap map;
    map.insert("apple", 1);
    map.insert("banana", 2);
    map.insertMulti("apple", 3);
  2. 自定义哈希函数: QHash 允许你为自定义类型提供哈希函数。你需要为你的类型实现一个名为 qHash 的全局函数,该函数接受你的类型作为参数并返回一个 uint 值。
    uint qHash(const CustomType &key, uint seed = 0) noexcept;
  3. 自定义比较函数: QHash 默认使用 operator== 进行键值比较。如果你需要使用自定义比较函数,可以使用 QHash 的一个变体——QHashF,它接受一个自定义的比较函数对象。
    QHash hash;
  4. 容量管理: QHash 允许你控制其底层哈希表的大小。使用 reserve() 函数预分配空间可以避免不必要的重新哈希。此外,capacity() 函数可以获取当前的容量,squeeze() 函数可以收缩容量以适应当前的元素数量。
    hash.reserve(100);
    int cap = hash.capacity();
    hash.squeeze();
  5. QMultiHash: QHash 不允许多个键关联到同一个值。QMultiHash 允许这种关联,支持添加和查找具有相同键的值。
    QMultiHash multiHash;
    multiHash.insert("key", 1);
    multiHash.insert("key", 2);
    QList values = multiHash.values("key");
  6. 遍历: QHash 提供了多种遍历方式。可以使用 C++11 范围迭代(range-for loop)遍历 QHash,也可以使用迭代器(iterator)进行遍历。
    for (const auto &key : hash.keys()) { ... }
    for (const auto &value : hash.values()) { ... }
    for (auto it = hash.begin(); it != hash.end(); ++it) { ... }
    
  7. 其他操作: QHash 提供了一些其他有用的操作,如交换(swap)、取差集(subtract)、合并(unite)等。
    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 的高级用法

QHash 是 Qt 库中的一个关联容器,类似于 C++ 标准库中的 std::unordered_map。QHash 用于存储键值对,通过键进行高效的查找、插入和删除操作。这里提供一些 QHash 的高级用法:

  1. 使用自定义类型作为键或值:

    要使用自定义类型作为 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;
  2. 使用 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);
  3. 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));}
    }
  4. QHash 的 value 初始化:

    在 QHash 中插入新键时,可以通过 QHash::operator[]QHash::insert 方法同时初始化对应的值。对于 operator[],如果键不存在,则会插入一个默认初始化的值。而 insert 则可以同时插入键和值。

    QHash myHash;
    myHash["key1"] = 42; // 使用 operator[] 插入键 "key1" 并初始化值为 42
    myHash.insert("key2", 100); // 使用 insert() 插入键 "key2" 和值 100
    
  5. QHash 的 value 更新:
    通过 QHash::operator[] 可以更新已存在键的值。如果键不存在,会插入一个新的键值对。此外,也可以使用 QHash::insert 方法更新已存在键的值。

    QHash myHash;
    myHash["key1"] = 42;
    myHash["key1"] = 100; // 使用 operator[] 更新 "key1" 的值为 100
    myHash.insert("key1", 200); // 使用 insert() 更新 "key1" 的值为 200
  6. 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();
    }
  7. QHash 的条件查找:

    可以使用 QHash::findQHash::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();}
    }
  8. 删除指定条件的键值对:

    使用 QHash::erase 方法删除满足特定条件的键值对。例如,删除值小于 10 的键值对:

    QHash myHash;
    // ... 添加一些键值对QHash::iterator i = myHash.begin();
    while (i != myHash.end()) {if (i.value() < 10) {i = myHash.erase(i);} else {++i;}
    }

迭代器:遍历 QHash 中的元素(Iterators: Traversing Elements in QHash )

在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和其他容器的对比

QHash 是 Qt 容器类中的一个关联容器,主要用于存储键值对。与其他容器相比,QHash 有其特点和优势。这里我们将 QHash 与其他 Qt 容器类和 C++ 标准库容器进行对比。

  1. QHash vs QMap (Qt 容器类)

QHash 和 QMap 都是 Qt 提供的关联容器,用于存储键值对。它们的主要区别在于底层实现和性能特点。

  • QHash:基于哈希表实现,具有平均 O(1) 的查找、插入和删除时间复杂度。但 QHash 的内存占用通常高于 QMap。QHash 中的键值对是无序的,即按照哈希值的顺序存储。
  • QMap:基于红黑树实现,具有 O(log n) 的查找、插入和删除时间复杂度。QMap 的内存占用通常低于 QHash。QMap 中的键值对是有序的,按照键的顺序存储。

选择 QHash 还是 QMap 取决于具体应用场景。如果需要快速查找、插入和删除操作,且不关心键值对的顺序,可以选择 QHash。如果需要按键顺序存储键值对,或者对内存占用有较高要求,可以选择 QMap。

  1. QHash vs std::unordered_map (C++ 标准库)

QHash 和 std::unordered_map 都是基于哈希表实现的关联容器,用于存储键值对。它们的主要区别在于 API 设计和内存管理策略。

  • QHash:Qt 容器类,API 设计遵循 Qt 的编程风格,与其他 Qt 容器和组件协同工作更为方便。QHash 使用引用计数和写时复制(Copy-On-Write)策略,可以在多个对象之间共享数据,从而节省内存和提高性能。
  • std::unordered_map:C++ 标准库容器,API 设计遵循 STL(Standard Template Library)的编程风格。std::unordered_map 与 Qt 容器类在 API 设计上有差异,但可以与其他 STL 容器和算法无缝协同工作。

在使用 Qt 应用开发时,如果需要与 Qt 的其他组件协同工作,QHash 可能是更好的选择。而在不涉及 Qt 组件的纯 C++ 项目中,可以考虑使用 std::unordered_map。

总之,选择合适的容器取决于具体的应用场景和性能需求。在实际项目中,可以根据需求进行测试和评估,以确定最适合的容器类型。

QHash 和 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的底层原理和内存管理

QHash 是 Qt 框架中的一个关联容器类,用于存储键值对。它的底层数据结构是哈希表,通过哈希函数将键映射到桶(bucket)中。下面详细介绍 QHash 的底层原理和内存管理。

底层原理:

  1. 哈希表:哈希表是一种使用哈希函数将键映射到桶中的数据结构。在 QHash 中,哈希表的每个桶都可以存储一个或多个键值对。当插入、查找或删除操作发生时,QHash 首先计算键的哈希值,然后使用这个哈希值找到对应的桶。
  2. 哈希冲突:由于哈希表中的桶数量有限,可能会发生多个键的哈希值映射到同一个桶的情况,这称为哈希冲突。QHash 使用链地址法来解决冲突。当多个键映射到同一个桶时,这些键值对会以链表的形式存储在桶内。QHash 会在链表中进行线性查找以找到对应的键值对。
  3. 动态扩容:为了维持哈希表的性能,QHash 会根据当前元素数量和桶数量之间的比值(负载因子)来动态调整哈希表的大小。当负载因子超过预设的最大负载因子时,QHash 会增加桶的数量并重新分配元素。扩容操作会导致一定的性能开销,但可以保证 QHash 的查找、插入和删除操作的平均时间复杂度为 O(1)。

内存管理:

  1. 隐式共享:QHash 使用隐式共享技术,也称为写时复制(Copy-On-Write, COW)。这意味着在拷贝 QHash 实例时,它们会共享底层数据。只有在需要修改数据时,才会创建一个实际的拷贝。这有助于减少内存占用和拷贝开销。
  2. 哈希表的内存分配:QHash 会动态分配和管理哈希表的内存。当插入新元素时,QHash 会根据需要分配额外的内存来存储元素。当元素被删除或 QHash 被清空时,相应的内存会被释放。通过调整最大负载因子,你可以在性能和内存占用之间取得平衡。
  3. 预留容量:如果你知道 QHash 将要存储的元素数量,可以使用 reserve() 函数预先分配足够的内存。这可以避免频繁的动态内存分配和释放,从而提高性能。预留容量只影响 QHash 的哈希表大小,不影响已存储的元素数量。当元素数量增长超过预留容量时,QHash 会自动进行扩容。
  4. 自定义内存管理:对于自定义类型作为键和值的 QHash,内存管理取决于这些自定义类型的实现。当你需要更精细的内存管理控制时,可以为自定义类型提供相应的构造函数、拷贝构造函数、移动构造函数、析构函数以及拷贝和移动赋值操作符。这些函数将决定自定义类型在 QHash 中的内存管理行为。

综上所述,QHash 的底层原理基于哈希表,可以实现快速查找、插入和删除操作。内存管理方面,QHash 使用隐式共享技术来减少内存占用和拷贝开销,同时可以通过调整负载因子和预留容量来平衡性能与内存占用。对于自定义类型,内存管理取决于类型的实现,你可以通过提供相应的函数来实现更精细的内存管理控制。

QHash 的性能分析:查找、插入与删除操作

QHash 是一个基于哈希表的关联容器,它的性能特点在于平均情况下快速的查找、插入和删除操作。以下是对 QHash 主要操作的性能分析:

  1. 查找(Find): 查找是 QHash 最常用的操作之一。在理想情况下,当哈希函数的分布很好时,查找的时间复杂度接近 O(1)。但是,当哈希冲突发生时,查找性能可能会降低。QHash 使用开放寻址法来解决冲突,这意味着在冲突时需要线性地查找下一个可用的槽。在哈希表的负载因子较高时,查找性能可能会受到影响。尽管如此,实际应用中 QHash 的查找性能通常仍然非常高。
  2. 插入(Insert): 插入操作的性能与查找操作类似。在理想情况下,插入操作的时间复杂度接近 O(1)。然而,当哈希冲突发生时,插入性能可能会降低。在高负载因子下,QHash 可能需要调整大小以保持良好的性能。调整大小会导致暂时的性能下降,因为需要重新分配内存并重新插入所有元素。
  3. 删除(Remove): 删除操作的性能同样依赖于哈希冲突的情况。在理想情况下,删除操作的时间复杂度接近 O(1)。然而,如果哈希冲突发生,删除性能可能会受到影响。QHash 使用开放寻址法来解决冲突,在删除元素时需要特别处理,以确保不会留下“空洞”,否则可能导致查找失败。因此,删除操作可能需要进行额外的操作来维护哈希表的完整性。

总之,QHash 在平均情况下为查找、插入和删除操作提供了非常高的性能。当哈希冲突较少时,这些操作的时间复杂度接近 O(1)。为了保持良好的性能,QHash 根据负载因子自动调整大小,以减少哈希冲突的可能性。然而,实际应用中,性能可能会受到哈希函数、负载因子和数据分布的影响。

使用QHash 可能遇到的问题和解决方案.

在使用 QHash 时,可能会遇到一些问题。以下是一些常见问题及其解决方案:

  1. 哈希冲突: 问题:当两个不同的键具有相同的哈希值时,会发生哈希冲突。这可能导致查找、插入和删除操作的性能下降。 解决方案:尽量使用具有良好分布特性的哈希函数。对于自定义数据类型,您可以根据其特性实现自定义哈希函数。同时,QHash 会自动调整大小以减少哈希冲突的可能性。
  2. 线程安全: 问题:QHash 本身不是线程安全的。在多线程环境中,同时访问同一个 QHash 实例可能导致数据竞争和不确定行为。 解决方案:使用 QMutex 或 QReadWriteLock 来同步对 QHash 的访问,确保一次只有一个线程访问 QHash。请参阅前面关于线程安全性和 QHash 并发使用的讨论。
  3. 内存占用: 问题:尽管 QHash 通常比 QMap 更节省内存,但在某些情况下,QHash 的内存占用可能仍然较高。这是因为 QHash 需要预先分配一定数量的槽,以便在插入新元素时保持较低的负载因子。 解决方案:如果内存使用是一个关键问题,可以考虑使用 QMap。虽然 QMap 的查找性能稍差(O(log n)),但它的内存占用通常较低。另一种选择是调整 QHash 的初始容量和负载因子,以便更好地平衡内存使用和性能。
  4. 自定义数据类型作为键: 问题:当使用自定义数据类型作为 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;
  5. 对不存在的键调用 value() 函数: 问题:在 QHash 中调用 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 {// 键不存在时的处理
    }
  6. 键的顺序: 问题:与 QMap 不同,QHash 不保证元素按照键的顺序存储。这可能导致问题,尤其是在需要按照键的顺序处理元素时。 解决方案:如果需要保持键的顺序,可以考虑使用 QMap。QMap 保证元素按照键的顺序存储,但查找、插入和删除操作的时间复杂度为 O(log n)。另一种方法是在处理 QHash 元素时,首先将键存储在一个单独的列表中,然后对列表进行排序。
  7. 元素的遍历顺序: 问题:QHash 的元素遍历顺序不固定,可能在每次运行时都不同。这可能导致程序行为不稳定或难以预测。 解决方案:如果元素的遍历顺序对程序逻辑很重要,可以考虑在遍历元素之前对键进行排序。然后,可以按照排序后的键列表遍历 QHash。
    QHash hash;
    // ... 填充 QHash ...// 获取所有键
    QStringList keys = hash.keys();// 对键进行排序
    keys.sort();// 按照排序后的键遍历 QHash
    for (const QString &key : keys) {ValueType value = hash.value(key);// 处理键值对
    }

QHash 的应用场景

QHash是Qt中一个高效的哈希表实现,用于存储键值对。以下是一些常见的应用场景:

  1. 缓存:QHash可用于缓存计算成本高、访问频繁的数据,以降低应用程序的延迟。例如,网络应用程序可以使用QHash来缓存已请求过的数据,从而减少不必要的网络请求。
  2. 计数器:QHash可以作为计数器,用于跟踪元素的出现次数。例如,在文本分析中,我们可以使用QHash来统计单词的出现次数。
  3. 查找表:QHash可用于实现快速查找。例如,在编译器或解释器中,我们可以用QHash来存储符号表,从而快速查找变量或函数的地址。
  4. 组织关联数据:QHash可以用于组织具有关联关系的数据。例如,在一个图形编辑器中,我们可以用QHash将图形元素与其属性关联起来。
  5. 状态跟踪:QHash可用于跟踪对象的状态,例如,在游戏中,我们可以用QHash来存储游戏角色的属性和当前状态。
  6. 事件分发:在事件驱动的应用程序中,QHash可以用于存储事件处理函数,从而实现事件的分发。
  7. 对象池:QHash可以用于实现对象池,从而提高内存分配的性能。例如,我们可以用QHash来存储重用的数据库连接。
  8. 对象工厂:QHash可用于实现对象工厂模式。例如,我们可以用QHash来存储类名与其构造函数的映射,从而动态地创建对象。

总之,QHash是一个灵活且高效的关联容器,适用于各种应用场景。根据需要,您可以使用QHash来简化代码、提高性能以及实现更好的数据组织。

实战案例:QHash 在实际项目中的应用(Practical Examples: QHash in Real-World Projects)

QHash是Qt中一个高效的关联容器,用于存储键值对。在实际项目中,QHash可以在多种场景下应用,以下是一些实际案例:

案例1:缓存

在实际项目中,我们经常需要缓存一些数据,以减少不必要的计算或网络请求。例如,假设我们正在开发一个获取用户个人资料的应用程序。为避免频繁发起网络请求,我们可以使用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) {// 发起网络请求,获取用户资料}
};

案例2:计数器

我们可以使用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;
};

案例3:对象属性

在某些情况下,我们可能需要为对象动态地添加或删除属性。这时,可以使用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 的并发使用(Thread Safety and Concurrent Usage of QHash )

QHash 本身不是线程安全的。在多个线程中访问或修改同一个 QHash 实例可能会导致不确定的行为或数据竞争。为了在多线程环境中使用 QHash,您需要采取一些措施来确保线程安全。

以下是一些在多线程环境中使用 QHash 的方法:

  1. 使用互斥量(QMutex):在对 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;
    };
  2. 使用读写锁(QReadWriteLock):如果您的应用程序主要是执行读操作,并且写操作相对较少,那么使用 QReadWriteLock 可能是更好的选择。读写锁允许多个线程同时进行读操作,但在执行写操作时,需要独占锁定。
    #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;
    };
  3. 使用并发容器(例如,QHash 和 Qt Concurrent 模块):Qt 提供了 QtConcurrent 模块,其中包含一些用于并发编程的类和函数。然而,Qt Concurrent 模块没有提供一个线程安全的哈希表实现,因此,您需要使用其他方法来实现线程安全的 QHash 访问。

请注意,在某些情况下,使用锁可能会导致性能下降。在这种情况下,您可能需要调查其他数据结构或并发技术,例如无锁编程、原子操作或线程局部存储。

QT各版本中QHash的变化

从 Qt5 到 Qt6,QHash 主要经历了以下变化:

  1. 容器的迭代器失效行为变得更加严格:在 Qt5 中,QHash 允许在迭代过程中对容器进行修改,尽管这种做法可能导致未定义行为。然而,在 Qt6 中,对 QHash 的修改可能会导致迭代器失效。因此,如果在迭代过程中需要修改容器,建议使用新的 QHash::take() 函数,它可以安全地删除元素,而不会导致迭代器失效。
  2. QHash 的内部实现发生了变化:在 Qt5.14 版本中,QHash 的内部实现发生了改变,改用 Robin Hood hashing 算法。这种新算法在处理碰撞时表现更好,有助于提高 QHash 的性能。这种改进在 Qt6 中保持不变。
  3. Qt6 提供了新的 QMultiHash 容器:在 Qt6 中,QHash 不再允许存储具有相同键的多个元素。因此,Qt6 提供了新的 QMultiHash 容器来替代 QHash 提供的多值功能。
  4. 移除了不再需要的成员函数:在 Qt6 中,QHash 移除了一些不再需要的成员函数,例如 qHash(),因为现在可以直接使用 qHashRange() 函数。此外,还移除了 QHash::operator[]() 的 const 版本,因为它的行为可能导致错误。替代方法是使用 QHash::value() 函数来获取键对应的值。
  5. QHash 的初始化语法改变:在 Qt5 中,可以使用初始化列表语法直接初始化 QHash。但是,在 Qt6 中,这种语法不再适用。为了实现类似的功能,Qt6 提供了 QHash::from() 函数。例如,在 Qt5 中可以使用如下代码:
    QHash hash = {{1, "one"},{2, "two"},{3, "three"}
    };
    在 Qt6 中,可以使用如下代码:
    QHash hash = QHash::from({{1, "one"},{2, "two"},{3, "three"}
    });

Qt5 到 Qt5.14

在这个时间段内,QHash 没有显著的变化。

Qt5.14 到 Qt5.15

在 Qt5.14 到 Qt5.15 期间,QHash 经历了一个主要的 API 更新:

  • 引入了 QHash::removeIf() 成员函数,这使得我们能基于特定条件从 QHash 中删除元素。

Qt5.15 到 Qt6.0

Qt6.0 是一个主要的版本更新,主要关注性能、内存使用和源代码兼容性。QHash 在此版本中有以下变化:

  1. QHash 的内部实现在 Qt6.0 中得到优化,实现了更高效的内存管理。此优化通过减小内存碎片和降低内存分配次数来降低内存使用。
  2. Qt6.0 引入了新的范围构造函数 QHash::QHash(InputIterator, InputIterator),允许从迭代器范围构造 QHash。
  3. 引入了 QHash::multiFind() 成员函数,返回一个给定键的值范围的迭代器对。这有助于更高效地在 QHash 中查找多个值。
  4. 为了提高源代码兼容性,Qt6.0 中的一些成员函数已被标记为 [[nodiscard]],例如 QHash::contains()QHash::isEmpty()
  5. 在 Qt6.0 中,QHash 的迭代器失去了运算符 ++ 和 – 的后置版本,使代码更简洁。

Qt6.0 到 Qt6.1

在 Qt6.0 到 Qt6.1 之间,QHash 没有显著的变化。

Qt6.1 到 Qt6.2

在 Qt6.1 到 Qt6.2 之间,QHash 也没有显著的变化。

请注意,这里总结的变化截至 2021 年 9 月,随着 Qt 版本的持续更新,可能会有新的变化和优化。要获取最新信息,请参考 Qt 的官方文档。

结语

亲爱的读者,感谢您陪伴我们一起深入了解 QHash 这个强大的数据结构。心理学告诉我们,学习新知识和技能对于保持我们大脑敏锐和适应不断变化的世界至关重要。事实上,学习是一种生存机制,有助于我们应对挑战并在社会中获得成功。

同样,QHash 的高效性和灵活性使我们在处理复杂问题时更加游刃有余,为我们提供了强大的工具来更好地理解和掌控我们周围的数字世界。就像心理学所强调的,我们的思维方式和行为模式对于我们的成长和发展有着深远的影响,而在技术领域,掌握 QHash 等高效工具能够为我们带来巨大的优势。

在此,我们邀请您将这篇博客收藏起来,以便在将来遇到相关问题时能迅速找到它。同时,如果您觉得这篇文章对您有所帮助,也请不吝点赞以示支持。这不仅会对作者产生积极的心理反馈,激励我们持续创作更多有价值的内容,同时也有助于更多的人找到这篇文章,共同探索知识的魅力。

最后,愿我们在不断学习的道路上共同进步,共创美好的未来。

Qt扫盲-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做键,所以不要慌。

Qt: QHash类的使用 汇总_第4张图片

二、使用
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
  QHash hash;
  ...
  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_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

 

在上面的例子中,我们依赖Qt的全局qHash(const QString &, uint)来为我们提供员工名字的哈希值,并将其与他们出生的日期异或,以帮助生成具有相同名字的人的唯一哈希值。
请注意,Qt提供的qHash()重载的实现可能随时改变。你不能指望qHash()在不同的Qt版本中(对于相同的输入)会得到相同的结果。
6.算法复杂性

Qt: QHash类的使用 汇总_第5张图片

 

所有的散列表都容易受到一类特定的拒绝服务攻击,攻击者会仔细地预先计算一组不同的键,这些键将被散列到散列表的同一个桶中(甚至具有相同的散列值)。攻击的目的是在数据被输入到表中时获得最坏情况下的算法行为(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

你可能感兴趣的:(qt,开发语言)