问题现象
情况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)); }
自此,怪异问题解决。
丢掉的数据找到了。