模板类HashMap【Key-Value】的结构对于Key的判断问题记录

问题现象

情况1:

由于两段代码的判断逻辑基本是一样的,所以两个HashMap的数据量一致。
当然不仅数量,数据也应该是完全一致的。

第一段代码(满足条件且HashMapA中 没有 的数据加载到HashMapA):

SomeKey Key;
for (Table.IterBegin() ; Table.IterNext() ; )
{
	if (!条件1 || !Key.set(Table.Field[xxx].Value) ||HashMapA.Find(Key)) continue;
	if (!条件2 || !条件3 || !条件4) continue;
	HashMapA.Add(Key);
}
//实际加载了10000条

第二段代码(满足条件且HashMapA中 的数据加载到HashMapB):

for (Table.IterBegin() ; Table.IterNext() ; )
{
	if (!条件1 || !Key.set(Table.Field[xxx].Value) || !HashMapA.Find(Key)) continue;
	if (!条件2 || !条件3 || !条件4) continue;
	HashMapB.Add(Key);
}
//实际加载了10000条

情况2:

第一段代码修改了两个if判断的顺序,居然数据量不等了!!!!
第一段代码(满足条件且HashMapA中 没有 的数据加载到HashMapA):

SomeKey Key;
for (Table.IterBegin() ; Table.IterNext() ; )
{
	if (!条件2 || !条件3 || !条件4) continue;
	if (!条件1 || !Key.set(Table.Field[xxx].Value) ||HashMapA.Find(Key)) continue;
	HashMapA.Add(Key);
}
//实际加载了10000条

第二段代码(满足条件且HashMapA中 的数据加载到HashMapB):

for (Table.IterBegin() ; Table.IterNext() ; )
{
	if (!条件1 || !Key.set(Table.Field[xxx].Value) || !HashMapA.Find(Key)) continue;
	if (!条件2 || !条件3 || !条件4) continue;
	HashMapB.Add(Key);
}
//实际加载了9994条

分析过程

推断

条件的顺序肯定是不影响逻辑的,那么问题一定出在HashMap.Add,或Find上。
由于HashMapA的数据量一直是10000没有变过,暂时推断Add没有问题。
先看看Find是否有问题。

验证

将两个HashMap加载的全部类容输出到文本文件,排序后比较。
果然HashMapA,没有发生任何变化。
而HashMapB在情况2中有6条数据不见了,假设一条其中一条叫MissingKey1。

调试

MissingKey1,也就是Key的结构如下:

struct SomeKey
{
        unsigned char Key[37];
		SomeKey() { memset(Key,0,sizeof(Key)); }
		SomeKey(const SomeKey& obj);
		SomeKey& operator=(const SomeKey& obj) { memcpy(Key, obj.Key, sizeof(Key)); return *this; }
		bool operator==(const SomeKey& obj) const { return memcmp(Key, obj.Key, sizeof(Key)) == 0; }
		myuint_t GetHash() const { return mymhashuA(Key, sizeof(Key)); }

                bool Set(const unsigned char* rawValue, myuint32_t rawSize)
                {
                     if(rawSize > 36 || rawSize <= 0) return false;
                     memcpy(Key,rawValue,rawSize);
                     Key[rawSize] = 0;
                     return true;
                }
                
                void Get(char* outvalue) const
                {
                     strcpy(outvalue, Key);
                }
};

发现非常简单,暂时看不出什么问题。
而通过跟踪程序,发现的确是HashMapA.Find(Key)的时候没找到。
那么为什么第一段代码修改顺序前就能找到呢?

继续分析

修改顺序前后,第一段代码唯一变化的就是

    Key.set(Table.Field[xxx].Value

这段语句,第一种情况下会比较多次的被执行,【敲黑板】。
但是由于下一个过滤条件,某些Key会被过滤掉不会加入HashMapA。
那我们来看看set:

                bool Set(const unsigned char* rawValue, myuint32_t rawSize)
                {
                     if(rawSize > 36 || rawSize <= 0) return false;
                     memcpy(Key,rawValue,rawSize);
                     Key[rawSize] = 0;
                     return true;
                }

内存拷贝最长36的字符串,并在最后添加0。
作为字符串的确没有什么问题。

再看HashMap是怎么Find的。
当然就是先GetHash出位置,再判断两个Key是否相等。
嗯,GetHash貌似没有问题(不想贴那么多代码了,mymhashuA也是基于字符串unsigned char*)。

判断两个Key是否相等,Key1==Key2.。。。纳尼?

bool operator==(const SomeKey& obj) const { return memcmp(Key, obj.Key, sizeof(Key)) == 0; }

等一下。。。

    memcmp(Key, obj.Key, sizeof(Key)) 

总算发现问题在哪里了,结构内部的Key是字符串,但判断是否相同是内存比较。

问题结论

先看看前面敲黑板的地方,修改代码顺序后,Key.set(),两段代码被执行的次数不一样。
也就是说,上一个被set的数据不一样。
那么MissingKey1符合条件准备被加入到HashMap时,如果比之前的Key长度短了的话。

这个MissingKey1在修改后的第一段代码的内容就会是:

    MissingKey1.key='13908085678\0未清空的上一条数据A一直铺满37个字节';

而在第二段代码里同一个MissingKey1的内容却是(上一条不一样啊):

    MissingKey1.key='13908085678\0未清空的上一条数据B一直铺满37个字节';

既然==是用的内存比较,那么当然就不一样啦!!!!

总结与修改

如果Key是定长的,不会出现这种情况。
如果Key是整型等,不会出现这种情况。
如果set前清空内存,也可避免这种情况:

                bool Set(const unsigned char* rawValue, myuint32_t rawSize)
                {
                     if(rawSize > 36 || rawSize <= 0) return false;
                     memset(Key,0,sizeof(Key));
                     memcpy(Key,rawValue,rawSize);
                     Key[rawSize] = 0;
                     return true;
                }

如果重载==用字符串比较,也可以避免:
Sorry, sorry… 如果要用字符串比较,则Hash也得改啊,写的时候没注意。。。

		bool operator==(const SomeKey& obj) const { return strcmp(Key, obj.Key) == 0; }
		myuint_t GetHash() const { return mymhashuA(Key, strlen(Key)); }

自此,怪异问题解决。
丢掉的数据找到了。

### C++ 中 Hashmap 的排序方法及其实现 在 C++ 标准库中,并不存在名为 `hashmap` 的容器,而是提供了 `unordered_map` 来作为哈希表的实现[^1]。由于哈希表本身的设计并不保证键值对的顺序,因此如果需要对其进行排序,则需借助其他数据结构或算法。 以下是基于标准库的功能实现 `unordered_map` 排序的方法: #### 方法一:通过自定义比较器并转换为有序容器 可以通过将 `unordered_map` 转换为支持排序的数据结构(如 `std::vector<std::pair>` 或 `std::multimap`),再利用 STL 提供的排序功能完成操作。 ```cpp #include <iostream> #include <unordered_map> #include <vector> #include <algorithm> int main() { std::unordered_map<int, int> umap = {{3, 30}, {1, 10}, {2, 20}}; // 将 unordered_map 转换为 vector<pair> std::vector<std::pair<int, int>> vec(umap.begin(), umap.end()); // 使用 sort 函数按 key 值升序排列 std::sort(vec.begin(), vec.end(), [](const auto &a, const auto &b) -> bool { return a.first < b.first; }); // 输出结果 for (auto &[key, value] : vec) { std::cout << "{" << key << ", " << value << "}\n"; } return 0; } ``` 此代码片段展示了如何先将 `unordered_map` 数据复制到一个向量中,随后调用 `std::sort()` 对其按键值进行排序[^2]。 #### 方法二:使用流式处理与 Lambda 表达式 另一种更现代的方式是采用范围适配器(Range Adapters)或者类似的函数式编程风格来简化逻辑。虽然这种方法不直接改变原容器的内容,但它能方便地生成已排序的结果集。 ```cpp #include <iostream> #include <unordered_map> #include <set> #include <functional> int main(){ std::unordered_map<int,std::string> data{{7,"seven"},{4,"four"},{8,"eight"}}; // 创建 multiset 存储 pair 并保持自然顺序 std::multiset<std::pair<const int&,std::string>,decltype([](const auto& lhs,const auto& rhs){return lhs.second<rhs.second;})> orderedData( [&](const auto& lhs,const auto& rhs){ return lhs.second < rhs.second; }, data.begin(), data.end() ); for(auto&& item:orderedData){ std::cout<<item.first<<"="<<item.second<<"\n"; } } ``` 这里我们创建了一个多集合 (`std::multiset`) ,它会依据给定的标准自动调整内部存储项的位置从而达到间接"排序"的目的[^3]。 #### 特殊情况下的优化考虑 对于某些特定场景下可能存在的性能需求,比如频繁访问但很少修改的情况,可以尝试预计算 hashcode 后手动管理链表节点位置等方式进一步提升效率;然而这些做法通常较为复杂且容易引入错误风险,在实际开发过程中应谨慎评估必要性和可行性后再决定是否实施[^5]。 总结来说,尽管 C++ 的 `unordered_map` 默认不具备内在次序特性,但是凭借强大的模板机制以及丰富的辅助工具类的支持完全可以灵活应对各种定制化的需求。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值