我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。
这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。
前面已经实现了set,已经能对付大部分情形了。但是由于是共享内存,变长数据类型都必须按照最大长度来设置,这就会浪费很多很多空间,物理内存很贵的嘛。
一、设计
所以我们需要实现一个池来保存变长数据。由于业务的特征,我们这个池不用考虑删除的情形,无效数据太多时我们用别的方法来解决。能根据业务特征来实现特定解决方案的程序员才是好程序员。
字符串池的第一个版本只用一个共享内存数组对象,添加数据时在私有内存构建映射关系查找已经存在的数据。
逻辑非常简单:
- 构建缓存,得到已经存在的数据的映射关系
- 在缓存中查找
- 存在则直接返回句柄
- 不存在则添加到最后,然后返回句柄
唯一要注意的细节是检查容量,如果剩余容量不足以存放则用空串填充,避免一个字符串放在两个块上,这会导致严重的BUG。
二、完整代码
代码:
//字符串池,模板参数T用于区分不同用途的池
template<typename T,int PI_N,typename T_USER_HEAD>
class StringPool : public T_ARRAY<char,PI_N,T_USER_HEAD >
{
public:
using T_ARRAY<char, PI_N, T_USER_HEAD >::GetHead;
using T_ARRAY<char, PI_N, T_USER_HEAD >::Get;
using T_ARRAY<char, PI_N, T_USER_HEAD >::IsPrivate;
using T_ARRAY<char, PI_N, T_USER_HEAD >::Capacity;
using T_ARRAY<char, PI_N, T_USER_HEAD >::Size;
public:
typedef typename T_ARRAY<char,PI_N,T_USER_HEAD >::HANDLE HANDLE;
private:
map<string,HANDLE> mapStringHandle;//缓存,用来
HANDLE & FindString(char const * str,HANDLE & h)const
{
T_SHM_SIZE i;
for(i=0,h.handle=0;i<GetHead()->size;i+=strlen(Get(i))+1)
{
if(0==strcmp(str,Get(i)))
{
h.handle=i;
return h;
}
}
h.handle=-1;
return h;
}
public:
StringPool(char const * name,int version=0):T_ARRAY<char,PI_N,T_USER_HEAD >(name,version){}
void RebuildCache()
{
HANDLE h;
long count=0;
long i;
thelog<<"重构字符串缓存......"<<endi;
ClearCache();
for(i=0;i<GetHead()->size;i+=strlen(Get(i))+1)
{
++count;
h.handle=i;
mapStringHandle[Get(i)]=h;
}
thelog<<"重构缓存完成,共 "<<mapStringHandle.size()<<" 个字符串"<<endi;
}
void ClearCache(){mapStringHandle.clear();}
virtual bool disableMutex()const{return true;}
virtual bool Report()const
{
string str;
str="";
char buf[2048];
thelog<<endl<<GetHead()->name.c_str()<<" 开始报告字符串池"<<" ......"<<endl;
sprintf(buf,"pHead=%p isPrivate=%d",GetHead(),(IsPrivate()?1:0));
theLog<<buf<<endl;
if(NULL!=GetHead())
{
sprintf(buf,"name=%s capacity=%ld,size=%ld(%.2f%%)\n",GetHead()->name.c_str(),Capacity(),Size(),100.*Size()/Capacity());
theLog<<buf<<endl;
T_SHM_SIZE i;
long count=0;
char const * last=NULL;
HANDLE h;
for(i=0;i<GetHead()->size;i+=strlen(Get(i))+1)
{
++count;
h.handle=i;
last=Get(h);
if(count<10)theLog<<last<<endl;
else if(10==count)theLog<<"......"<<endl;
else continue;
}
if(NULL!=last)theLog<<"最后一个["<<last<<"]"<<endl;
theLog<<"字符串总数 "<<count<<endl;
}
theLog<<"报告完毕"<<endi;
return true;
}
virtual bool ExportTextToDir(char const * dir_name)const
{
thelog<<"字符串池不需要导出文本"<<endi;
return true;
}
bool AddString(char const * str,HANDLE & h)
{
//先检查是否已经存在
//FindString(str,h);
typename map<string,HANDLE>::iterator it=mapStringHandle.find(str);
if(it!=mapStringHandle.end())
{
h=it->second;
return true;
}
//检查剩余空间是否足够,若不够将剩余空间用0填充,避免一个字符串存储到两个块
//共享内存增长大小应该确保大于最长的字符串
T_ARRAY<char,PI_N,T_USER_HEAD > * pArray=this;
if(pArray->Capacity()-pArray->Size()<strlen(str)+1)
{
for(T_SHM_SIZE i=pArray->Size();i<pArray->Capacity();++i)
{
this->Add('\0',h);
}
}
//加入字符串
if(this->Adds(str,strlen(str)+1,h))
{
mapStringHandle[str]=h;
return true;
}
else return false;
}
char const * GetString(HANDLE const & h)const{return Get(h);}
};
三、其它
在set里面只需要保存字符串池的句柄即可。
字符串池也是模板,第一个参数是个类型,不过似乎也没什么实际意义,通过PI_N就足以区分了(每个实际类型都隐含关联到一个私有数据来进行地址转换)。
四、另一个版本
(待续)