14、基于共享内存的字符串池

初级代码游戏的专栏介绍与文章目录-CSDN博客

我的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就足以区分了(每个实际类型都隐含关联到一个私有数据来进行地址转换)。

四、另一个版本


(待续)

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值