2007年6月17日
#
前些天在做一个小项目,需要实现从字符串到XML文件的逆向转换过程。该字符串由XML文件所得。由于使用环境对解析时间和内存使用量有严格的要求,因此必须确保解析速度和所占用内存。
下面简单叙述一下我的实现过程。最开始采用的方法是每次从文件字符串里面读入一个节点的值,具体读取过程有xml文件各个节点属性决定。再利用一个stack对xml文件节点进行管理。大致思路是每读入一个字符串,先判断其类型,如果是Element或者text, comment, cdata类型则入栈,若为EndElement则出栈,这样就可以顺利建立起各个父子节点之间的关系。
采用这样的方法是思路比较的明确,实现起来比较的简单,缺点是解析速度太慢了,解析一个2M左右的XML文件要10多分钟,而且所费时间与文件的大小成几何级别增长,根本不可能接受。在采用这种方法过程中,也出现了一个小插曲。就是在解析比较大的xml文件时,当解析的xml节点超过1500个时,就会导致内存分配错误,堆栈溢出,开始是百思不得其解,后来才知道是由于我在解析字符串过程中,采用了递归的方法,因此内存消耗很厉害,特别是我开始传入一个const字符串时,一个小小的几百K(以200k为例)的文件就可能导致内存一下子消耗几百M,因为每次只读入一个节点字符串,这样最终大小可以达到200K+19.96k+....+0 ~=200*(200-1)k/2~ = 200M.因此导致编译器堆栈溢出,解决方法有几种,一是将堆栈设置大些,另外就是改递归为循环。我采用了后者。
在进行字符串解析时,我大量采用了STL的字符串find,find_first_of(),substr等
函数,但是这通常只在搜索小字符串时速度较快,在长达几M的字符串时,由于大块的内存操作,程序运行慢如蜗牛。而且我在前面的实现方法中,每次是提取一个节点,然后再进行解析,这样在读取和解析过程中,会导致许多重复的步骤,严重影响工作效率。 于是我就采用一个了for循环对读入的一个个字节进行处理,这样速度得到显著的提高。但是程序在解析大字符串时还是运行很慢,我开始 意识到是长字符串的问题,因此得想方法分段解析才行。于是决定每次从字符串里面提取一定的字符处理。在解析长达几M的字符串时,我先后试验了每次提取64bit,128bit,256bit,512bit, 1k, 2k, 4k等不同长度的字符串,发现在处理大字符串时,4K的效果最好。在解析一个8M左右的xml字符串时,速度可以达到30S,但是内存消耗有点厉害了,达100M。因此也很难满足要求。
最后还是采用了一种比较折中的方法,就是在初次解析时,只解析根节点以及其下一层子节点,在保存过程中再分段解析,主要可以极大的减少内存消耗,8M左右的文件可以降低到20M左右内存。速度也有所提高,最终耗时3s左右。
2007年5月3日
#
In these days, I used STL in a project, and met several problems. One was caused by erase method. As we know, when we delete a element in a container in STL, the iterator itself will be changed, so the the iterator should be set correctly. A right method can be used as following:
Typedef list<MyClass*>::Iterator myIter;
for(myIter it = listObj.begin(); it != listObj.end();)
if(true)
it = listObj.erase(it);
else
++it;
最近因为项目的需要,将一个应用软件的底层XML处理模块进行重写,由MSDOM改用xmlLite来完成。XmlLite是微软专门针对C++使用者开发的一个轻量级开发包,只具备基本的I/O功能。提供了IXmlReader, IXmlWriter对XML文件进行简单的读写操作。原理很简单,在读一个文件时,循环读取各个节点,然后根据不同的节点类型读取其相关属性数据等。XMLLite中的数据类型主要封装在XmlNodeType中,常使用到的有XmlNodeType_None, XmlNodeType_Element,XmlNodeType_EndElement等。在写数据时,主要根据不同的节点类型,调用相关的API来完成。值得注意的是,由于XMLLite只提供顺序化写的功能,因此在写具有多个深度的节点类型时,需要控制好WriteEndElement()函数的出现顺序等,所以这些都可以通过函数的递归来完成。
由于XmlLite只提供简单的读写等功能,因此,在实际应用中,需要对XMLLite提供的功能进行一定的封装,从而提供自己的API功能。下面简单说说我们采用的思路。在读Xml文件时,需要在加载过程建立XML文件的内部数据结构。这可以通过两种方式来完成,一种是在一个循环或者递归过程中,将整个XMLload进来;另外一种方法是一次只加载一层节点,然后递归加载其子节点。前面一种方法是在处理大XML文件时,可能会有memory footprint问题。所以最终采用了后面的方法。
在实现过程中,我们采用了composite模式来组织XML文件树结构。通过使用list来建立树结构。全部操作封装在一个类中。
有关相关原因,xmlLite的具体封装实现方法就不提及了。开发过程中,遇到的主要难点是数据的读写和保存,关键是数据结构的处理,其他部分都比较容易。
这我开通blog后的第一篇文章,呵呵,也不知道怎么写好。以后会尽力写好点^_^.