QHash源码解读

文章详细介绍了QT库中QHashData结构的内部实现,包括QHashData的成员变量和相关操作,如字节对齐的内存分配与释放,分离辅助函数detach_helper,以及涉及内存优化的重哈希函数rehash。QHashNode的结构设计确保了键值对的存储效率,且在哈希冲突时能有效处理。重哈希过程仅需一次内存分配,提高了性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

QT版本 v5.12.10
元素
// 重点说明QHashData的函数,QHashData是QHash的基础
struct QHashData
{
    struct Node {
        Node *next;
        uint h;
    };

    Node *fakeNext;		// 永为null
    Node **buckets;		// Node *数组
    QtPrivate::RefCount ref;
    int size;			// node个数
    int nodeSize;		// node字节对齐后的大小
    short userNumBits;	// 用户设定的bit数,但不一定会取用
    short numBits;		// 位数,通过numBits可以计算出numBuckets的值,计算方法参考:http://oeis.org/A092131
    int numBuckets;		// 桶的总数量,为素数
    uint seed;			// hash计算时的种子
    uint sharable : 1;	// 是否共享,默认共享
    uint strictAlignment : 1;	// 是否字节对齐, 默认对齐
    uint reserved : 30;	// 保留字段

    void *allocateNode(int nodeAlign);
    void freeNode(void *node);
    QHashData *detach_helper(void (*node_duplicate)(Node *, void *), void (*node_delete)(Node *),
                             int nodeSize, int nodeAlign);
    void rehash(int hint);
    void free_helper(void (*node_delete)(Node *));
    Node *firstNode();
    static Node *nextNode(Node *node);
    static Node *previousNode(Node *node);

    static const QHashData shared_null;
};

template <class Key, class T>
struct QHashNode
{
    QHashNode *next;
    const uint h;
    const Key key;
    T value;

    inline QHashNode(const Key &key0, const T &value0, uint hash, QHashNode *n)
        : next(n), h(hash), key(key0), value(value0) {}
    inline bool same_key(uint h0, const Key &key0) const { return h0 == h && key0 == key; }

private:
    Q_DISABLE_COPY(QHashNode)
};
allocateNode & freeNode

字节对齐时的申请释放内存,字节对齐通过aligneof计算

detach_helper

当两个QHash存在共享且一个QHash产生写操作时,QHash调用此函数来完成分离操作。
具体操作是遍历每个桶及每个桶的链表,复制node插入到新的链表

rehash
void QHashData::rehash(int hint)
{
	// 以下代码找出合适的bit数,
    if (hint < 0) {
        hint = countBits(-hint);
        if (hint < MinNumBits)
            hint = MinNumBits;
        userNumBits = hint;
        while (primeForNumBits(hint) < (size >> 1))
            ++hint;
    } else if (hint < MinNumBits) {
        hint = MinNumBits;
    }
	
	// 此时hint为4 - 31其中一个(其实只到26,后面五个为0,参考prime_deltas)
    if (numBits != hint) {
        Node *e = reinterpret_cast<Node *>(this); // 将QHashData强转为Node类型,作为end标志
        Node **oldBuckets = buckets;
        int oldNumBuckets = numBuckets;

        int nb = primeForNumBits(hint);
        buckets = new Node *[nb]; // 创建新的桶
        numBits = hint;
        numBuckets = nb;
        for (int i = 0; i < numBuckets; ++i) // 为每个桶初始化,第一个元素都指向end
            buckets[i] = e;

		// 以下代码,将旧桶的链表打破,放入到新桶中,所以rehash所涉及的内存申请只有(n * 8)
		// 并不会为每个node都申请一遍
        for (int i = 0; i < oldNumBuckets; ++i) {
            Node *firstNode = oldBuckets[i];
            while (firstNode != e) {
                uint h = firstNode->h;
                Node *lastNode = firstNode;
                // 以下代码是找出同一个桶中相同hash值的末尾节点
                while (lastNode->next != e && lastNode->next->h == h)
                    lastNode = lastNode->next;

                Node *afterLastNode = lastNode->next; // maybe afterLastNode == end
                Node **beforeFirstNode = &buckets[h % numBuckets];
                while (*beforeFirstNode != e) // 找到新桶的最后一个节点
                    beforeFirstNode = &(*beforeFirstNode)->next;
                // 尾插法
                lastNode->next = *beforeFirstNode;
                *beforeFirstNode = firstNode;

                firstNode = afterLastNode;
            }
        }
        delete [] oldBuckets;
    }
}
设计细节

1、每个桶的结尾都指向QHashData,以此来判断是否到达结尾
为什么可以使用QHashData作为结尾呢?
因为QHashData的fakeNext变量放在第一个,fakeNext永为null,当将QHashData强转成Node类型时,Node的next值正好等于fakeNext,当判断到Node->next == nullptr时就可知道到达结尾,另外对于任意一个Node,都可以获取到QHashData的指针,nextNode,previousNode就是利用此特点
2、rehash涉及的内存只有n * sizeof(void *)大小
rehash函数中先创建n个桶,并初始化,然后打断旧桶的链表,根据hash值放入到新的桶
3、QHashNode的前两个变量正好对应Node的变量(类似linux下的双向链表),这样做就使得键值的类型与操作无关
4、暂时只想到这么多

结构图

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值