我们正常定义的 [键-->值] 类型数据容器 大多数都是用std::map, 或者std::std::unordered_map, 但是有些时候对其操作不太方便,特别是嵌套调用的时候进行添加删除操作,比如说:我有一个技能队列用的是std::std::unordered_map,当我们在帧循环中遍历技能队列数据,执行技能操作的时候,触发了一个新的技能或者删除了一个技能,这个时候在遍历的队列内部这个样就会出错:
// 错误示例:
std::unorder_map<int, skill_info> m_mapSkill;
void OnSkillUpdate()
{
for (auto iter = m_mapSkill.begin(); iter != m_mapSkill.end(); ++iter)
{
// 执行逻辑
// todo
// 触发了一个新的技能
// 最后要执行:
m_mapSkill.insert(std::make_pair(id, skill_info()));
// 或者:
m_mapSkill.erase()
// 上面这行代码就出问题了,
}
}
如果有这样的代码我们就要在执行遍历之前先把数据拷贝到另外一个零时队列,对零时队列进行遍历。或者对于添加或者删除操作添加到零时的容器中,在遍历完成以后再做添加或者删除操作。这种操作会破坏代码的整洁性,也添加了逻辑的复杂度。
第二是我写了一种新的通用数据结构,作为所有游戏对象的数据容器集合,有统一的查询、赋值和存储接口。需要定制特殊的容器,所以写了特殊的hashmap。可能下一次会讲下通用数据结构设计。
下面我讲下自定义hash map 实现逻辑:
hash map 的原理是定义一个数组,每个数组中是一个链表。 在插入操作时把键转换成int类型的hash值。然后这个hash值除以数组的大小,余数就落在了数组上面。数据根据余数存到数组对应的链表里。理想情况每个链表里就一个数据,不存在遍历操作。当然如果hash 值冲突,一个链表上的数据可能会不止一个。这个要看hash因子的设值,和hash值的计算。 现在很多库会把链表换成红黑树,加快查找速度。我觉得我的这个设计里面就没有必要了。
我自定义的hash map 在插入的时候会有 键,类型,和值三个参数, 键是唯一的,但是类型可能相同。我们可以根据唯一的键查找内容,也可以根据类型,遍历同一种类型的所有数据。而在删除的时候不是立即删除,而是把数据并行的添加到删除链表中。然后在帧循环中调用Hashmap 的update() 方法,进行实际的删除操作。添加的时候直接添加,不会在内部扩充容量,是在Hashmap的update()方法中检查是否需要扩充容量。
// 定制 hash 容器
/************************************************************************/
/*
*/
/************************************************************************/
#ifndef CoreHashMap_h__
#define CoreHashMap_h__
#include <string>
#include <assert.h>
#include "CoreLink.h"
#include <new.h>
#include <functional>
#include "jemalloc.h"
#ifndef _WIN32
#include <stdlib.h>
#include <string.h>
#endif
// hash 值计算与比较
template <typename TYPE>
class HashComp
{
public:
static size_t GetHashValue(const TYPE& t);
static bool Equal(const TYPE& t1, const TYPE& t2);
};
template<>
class HashComp<int>
{
public:
static size_t GetHashValue(const int& t)
{
return size_t(t);
}
static bool Equal(const int& t1, const int& t2)
{
return t1 == t2;
}
};
template<>
class HashComp<long>
{
public:
static size_t GetHashValue(const long& t)
{
return size_t(t);
}
static bool Equal(const long& t1, const long& t2)
{
return t1 == t2;
}
};
template<>
class HashComp<long long>
{
public:
static size_t GetHashValue(const long long& t)
{
return size_t(t);
}
static bool Equal(const long long& t1, const long long& t2)
{
return t1 == t2;
}
};
template<>
class HashComp<unsigned int>
{
public:
static size_t GetHashValue(const unsigned int& t)
{
return size_t(t);
}
static bool Equal(const unsigned int& t1, const unsigned int& t2)
{
return t1 == t2;
}
};
template<>
class HashComp<unsigned long>
{
public:
static size_t GetHashValue(const unsigned long& t)
{
return size_t(t);
}
static bool Equal(const unsigned long& t1, const unsigned long& t2)
{
return t1 == t2;
}
};
template<>
class HashComp<unsigned long long>
{
public:
static size_t GetHashValue(const unsigned long long& t)
{
return size_t(t);
}
static bool Equal(const unsigned long long& t1, const unsigned long long& t2)
{
return t1 == t2;
}
};
template<>
class HashComp<unsigned short>
{
public:
static size_t GetHashValue(const unsigned short& t)
{
return size_t(t);
}
static bool Equal(const unsigned short& t1, const unsigned short& t2)
{
return t1 == t2;
}
};
template<>
class HashComp<short>
{
public:
static size_t GetHashValue(const short& t)
{
return size_t(t);
}
static bool Equal(const short& t1, const short& t2)
{
return t1 == t2;
}
};
template<>
class HashComp<std::string>
{
public:
static size_t GetHashValue(const std::string& t)
{
assert(!t.empty());
size_t hv = 0;
for (int i = 0; t[i]; ++i)
{
hv = hv * 131 + t[i];
}
return hv;
}
static bool Equal(const std::string& t1, const std::string& t2)
{
return t1 == t2;
}
};
template<>
class HashComp<std::wstring>
{
public:
static size_t GetHashValue(const std::wstring& t)
{
assert(!t.empty());
size_t hv = 0;
for (int i = 0; t[i]; ++i)
{
hv = hv * 131 + t[i];
}
return hv;
}
static bool Equal(const std::wstring& t1, const std::wstring& t2)
{
return t1 == t2;
}
};
// 初始化容器个数
#define INIT_NODE_SIZE 8
//