如何散列 QVariant?
How to hash QVariant?
我需要使用 QList<QVariant>
作为 std::unordered_map
的密钥。这样做的目的是通过在唯一键列上建立索引来优化对 table 数据的搜索。
所以我做了这段代码。它不完整,但列出了 table 键列中出现的一些基本数据类型:
#include <unordered_map>
#include <string>
//std::hash
#include <functional>
//std::size_t
#include <cstddef>
// Hashing method for QVariantList
namespace std {
template <>
struct hash<QList<QVariant>>
{
std::size_t operator()(const QList<QVariant>& k) const
{
using std::size_t;
using std::hash;
using std::string;
size_t hash_num = 0;
Q_FOREACH(var, k) {
// Make hash of the primitive value of the QVariant
switch(var.type()) {
case QVariant::String : {
hash_num = hash_num^hash<string>(var.toString().toStdString());
break;
}
case QVariant::Char :
case QVariant::ULongLong :
case QVariant::UInt :
case QVariant::LongLong :
case QVariant::Int : {
hash_num = hash_num^hash<long long>(var.toLongLong());
break;
}
case QVariant::Double : {
hash_num = hash_num^hash<double>(var.toDouble());
break;
}
}
}
return hash_num;
}
};
}
显然,我不喜欢整个 switch
事情。这是一段又长又难看的代码,而且只说明了基本类型。我宁愿对分配给 QVariant
的内部数据的内存数据进行哈希处理。或者,甚至更好 - 使用一些 Qt 的散列方法。
是否有一种半可靠*的方法来散列任何 QVariant 而无需将其转换为原始类型?
*我知道复杂的对象可能隐藏在 QVariant 后面,但是这会导致碰撞的情况很少见,所以我不必关心。
给自己一个 QByteArray
+ QBuffer
+ QDataStream
来基本上将 QVariant
序列化到 QByteArray
.
然后简单地散列字节数组中的原始字节。 Qt 已经为 QByteArray
实现了一个 qHash
函数,所以你已经准备好了。
您可以通过重复使用具有足够预分配字节的相同 QByteArray
来最大限度地提高效率,以避免重新分配。您可以将整个内容包装在 VariantHasher
class 中,并在每次新的散列之前将缓冲区简单地 seek(0)
并且只散列 pos()
个字节而不是整个内容。
class QVariantHasher {
public:
QVariantHasher() : buff(&bb), ds(&buff) {
bb.reserve(1000);
buff.open(QIODevice::WriteOnly);
}
uint hash(const QVariant & v) {
buff.seek(0);
ds << v;
return qHashBits(bb.constData(), buff.pos());
}
private:
QByteArray bb;
QBuffer buff;
QDataStream ds;
};
如评论中所述,它非常快,并且它的优点是可以处理支持 QDataStream
序列化的每种类型。对于自定义类型,您只需要实现序列化,无需创建和维护一个巨大的开关。如果您已经实现了切换版本,那么进行比较会很有趣。 switch 本身有很多分支,而重用相同的字节数组对缓存非常友好,特别是如果你不使用很多字节,也就是说,你不是散列包含很长字符串或数组的变体。
此外,它比半可靠更好,因为散列也包括变体类型,所以即使在实际数据可能二进制相同的情况下,例如两个字节的值为 255 vs short 的值为65535,哈希将包含类型,因此值不会发生冲突。
我需要使用 QList<QVariant>
作为 std::unordered_map
的密钥。这样做的目的是通过在唯一键列上建立索引来优化对 table 数据的搜索。
所以我做了这段代码。它不完整,但列出了 table 键列中出现的一些基本数据类型:
#include <unordered_map>
#include <string>
//std::hash
#include <functional>
//std::size_t
#include <cstddef>
// Hashing method for QVariantList
namespace std {
template <>
struct hash<QList<QVariant>>
{
std::size_t operator()(const QList<QVariant>& k) const
{
using std::size_t;
using std::hash;
using std::string;
size_t hash_num = 0;
Q_FOREACH(var, k) {
// Make hash of the primitive value of the QVariant
switch(var.type()) {
case QVariant::String : {
hash_num = hash_num^hash<string>(var.toString().toStdString());
break;
}
case QVariant::Char :
case QVariant::ULongLong :
case QVariant::UInt :
case QVariant::LongLong :
case QVariant::Int : {
hash_num = hash_num^hash<long long>(var.toLongLong());
break;
}
case QVariant::Double : {
hash_num = hash_num^hash<double>(var.toDouble());
break;
}
}
}
return hash_num;
}
};
}
显然,我不喜欢整个 switch
事情。这是一段又长又难看的代码,而且只说明了基本类型。我宁愿对分配给 QVariant
的内部数据的内存数据进行哈希处理。或者,甚至更好 - 使用一些 Qt 的散列方法。
是否有一种半可靠*的方法来散列任何 QVariant 而无需将其转换为原始类型?
*我知道复杂的对象可能隐藏在 QVariant 后面,但是这会导致碰撞的情况很少见,所以我不必关心。
给自己一个 QByteArray
+ QBuffer
+ QDataStream
来基本上将 QVariant
序列化到 QByteArray
.
然后简单地散列字节数组中的原始字节。 Qt 已经为 QByteArray
实现了一个 qHash
函数,所以你已经准备好了。
您可以通过重复使用具有足够预分配字节的相同 QByteArray
来最大限度地提高效率,以避免重新分配。您可以将整个内容包装在 VariantHasher
class 中,并在每次新的散列之前将缓冲区简单地 seek(0)
并且只散列 pos()
个字节而不是整个内容。
class QVariantHasher {
public:
QVariantHasher() : buff(&bb), ds(&buff) {
bb.reserve(1000);
buff.open(QIODevice::WriteOnly);
}
uint hash(const QVariant & v) {
buff.seek(0);
ds << v;
return qHashBits(bb.constData(), buff.pos());
}
private:
QByteArray bb;
QBuffer buff;
QDataStream ds;
};
如评论中所述,它非常快,并且它的优点是可以处理支持 QDataStream
序列化的每种类型。对于自定义类型,您只需要实现序列化,无需创建和维护一个巨大的开关。如果您已经实现了切换版本,那么进行比较会很有趣。 switch 本身有很多分支,而重用相同的字节数组对缓存非常友好,特别是如果你不使用很多字节,也就是说,你不是散列包含很长字符串或数组的变体。
此外,它比半可靠更好,因为散列也包括变体类型,所以即使在实际数据可能二进制相同的情况下,例如两个字节的值为 255 vs short 的值为65535,哈希将包含类型,因此值不会发生冲突。