迷你5207专属论坛

注册

 

发新话题 回复该主题

[mininote] MiniNote1.0代码分析之文本存取器 [复制链接]

发表者

MiniNote1.0代码分析之文本存取器


原文:http://www.mini188.com/showtopic-1148.aspx(原文中有pdf版本可以下载)

一、    概述
文本存取器(M8NoteFileReader)是MiniNote1.0的核组件,承担了主要的存储工作。在1.0中文本存取器实现了ANSI、Uncode两种编码格式的文本文件读写。
在开发M8NoteFileReader的过程中也是经过了多个版本,最初MiniNote是采用的是ini文件实现数据的保存,但最后发现ini文件“键=值”形式结构文件,无法支持换行符。后来才采用了txt文件代替。在此也告诉一些新手朋友们,不要用ini文件存取长文本,ini用于软件参数配置的存储是非常好的。

二、    功能说明(方法)
M8NoteFileReader功能非常简单,既然叫文本存取器,也就是说主要就是“保存”和“读取”。类及方法申明请查看源代码,在此就不再贴出来。

既然是读取文件当然要知道文件的位置,对于M8NoteFileReader来说,它并关心如何取得文件的位置,只需要调用者将文件位置传入即可。M8NoteFileReader提供了一个OpenFile方法用于打开一份文本文件,方法申明如下:
  1. virtual bool OpenFile(TCHAR *filename);
复制代码
参数filename就是需要外部调用者传入的文本文件位置。在OpenFile方法中进行了哪些处理尼?
  1. //打开文本文件
  2. bool M8NoteFileReader::OpenFile(TCHAR *filename)
  3. {
  4.     m_TextCode = ttcUnicode;//默认为unicode
  5.     m_FileName = filename;
  6.     GetTextCode(m_FileName);

  7.     if (m_TextCode == ttcUnicode)
  8.     {
  9.         ReadFileByUnicode();
  10.     }
  11.     else if (m_TextCode == ttcAnsi)
  12.     {
  13.         ReadFileByAnsi();        
  14.     }

  15.     return true;
  16. }
复制代码
方法中主要分为二部分:初始化变量、根据编码类型读取文件。
进入到OpenFile后,第一步进行了变量的初始化,即OpenFile的前三行代码。前两行就不解释了,直接看GetTextCode(m_FileName),这句代码是用于获取文件的编码格式,进入到GetTextCode的代码中:
  1. void M8NoteFileReader::GetTextCode(TCHAR *filename)
  2. {
  3.     if (FileExists(filename))
  4.     {
  5.         fstream openfile;//创建文件流对象
  6.         openfile.open(filename, ios::in | ios::binary);//加载文件
  7.         openfile.seekg(0, ios::beg);//移动到首字节
  8.         char *code = new char[2];//创建一个两字节的字符串存放BOM
  9.         openfile.read(code, 2);//从流中读取前两个字节到字符串中
  10.         openfile.close();//关闭流对象,释放文件资源
  11.         
  12.         unsigned char cc = (unsigned char)code[0];//将BOM的第一个字节转换为一个byte
  13.         unsigned char cc1 = (unsigned char)code[1]; //将BOM的第二个字节转换为一个byte
  14.         if ((cc == 0xFF) && (cc1 == 0xFE))
  15.         {
  16.             m_TextCode = ttcUnicode;
  17.         }
  18.         else if ((cc == 0xFE) && (cc1 == 0xFF))
  19.         {
  20.             m_TextCode = ttcUnicodeBigEndian;
  21.         }
  22.         else if ((cc == 0xEF) && (cc1 == 0xBB))
  23.         {
  24.             m_TextCode = ttcUnicode;
  25.         }
  26.         else
  27.         {
  28.             m_TextCode = ttcAnsi;
  29.         }
  30.     }
  31. }
复制代码
在代码最开始就判断了文件是否存在,这是参数有效的验证,如果存在则会创建一个fstream对象,这个对象是用于处理流的对象,fstream可以将文件以二进制流的形式进行读取,程序员便可以方便的进行各种操作了。

在openfile.open(filename, ios::in | ios::binary)这句代码就是将文件加载到流的过程,其中第二参数很重要,ios::in表示只读,ios::binary表示二进制形式,还有其他类型具体请查看MSDN。

Open成功后应该有返回值,在这个程序中没有对返回值做检查这是一个问题,如果出现返回失败的情况接下来的代码就有可能报错,所以在注意了。

    接着将文件的前两个字节读取出来,这两个字节就是BOM结构啦。所谓BOM结构就是用于描述文本文件的编码规格的,这样程序在读取时就能使用正确的编码解析文件了。BOM结构的说明请查看《》,在上面的代码中可以看到,对BOM结构进行判断后就可以设置编码类型了。可能到这了会一个疑问,这个m_TextCode是个什么东西?这个是我申明的一个枚举,如下:
  1. //字符编码类型  
  2. enum TextTypeCode{ttcAnsi, ttcUnicode, ttcUnicodeBigEndian, ttcUtf8};
复制代码
将编码的类型列为枚举这样在编程时易于阅读及修改。

好了,回到OpenFile这个方法中,在获取到编码类型后就可以针对不同类型的文件进行读取解析了。由于MiniNote1.0只支持了Ansi和UniCode两种编码的文本文件,所以在if结构就只有两个分支。
第一个分支是Unicode,为什么放在前面呢?因为wince中默认是unicode,所以大部分文本都是以unicode为主的,进入到这个分支后会调用:ReadFileByUnicode()方法。

ReadFileByUnicode方法是M8NoteFileReader的一个方法,方法名已经说明了它是读取unicode文本文件的。于是进入到方法里面:
  1. void M8NoteFileReader::ReadFileByUnicode()
  2. {
  3.     wifstream ofile;
  4.     ofile.open(m_FileName.C_Str(), ios::in | ios ::binary);
  5.     if (ofile.is_open())//是否打开了文件?
  6.     {
  7.         ofile.seekg(0, ios::end);
  8.         int nLen = ofile.tellg();//获取文件的长度
  9.         ofile.seekg(2, ios::beg);//跳过BOM结构
  10.         TCHAR *tmpstr = new TCHAR[nLen];//申请一个字符串用于存放文件内容地
  11.         wmemset(tmpstr, 0, nLen);//初始化置0
  12.         ofile.read(tmpstr, nLen);//读取文件内容到字符串中
  13.         tmpstr[nLen] = '\0\0';//加上字符串结束符
  14.         m_Text = tmpstr;//将读取的内容写入类成员中,供外部访问
  15.         delete [] tmpstr;    //清理字符串
  16.     }
  17.     ofile.close();//释放文件资源
  18. }
复制代码
每行代码我都添加了注释以便于理解,在这里着重说明一个wifstream这个类,wifstream是fstream的一个子类,多了wi是啥意思呢?w表示wide就是指宽字符,因为unicode是双字节的都叫宽字符。I指的是啥我还真不知道,但是前面在讲fstream的open方法时提到了ios::in,大概和这个意思差不多。就是读取的意思。这下就清楚了,wifstream就是用于读取宽字符的类。因为这个宽字符的读取可是折腾了我好一阵子。

说完了ReadFileByUnicode,再回到OpenFile方法里,接着看另一个分支ReadFileByAnsi,代码如下:
  1. void M8NoteFileReader::ReadFileByAnsi()
  2. {
  3.     //读取文本
  4.     ifstream file1;
  5.     file1.open(m_FileName.C_Str(),  ios::in | ios::binary);
  6.     if (file1.is_open())
  7.     {
  8.         file1.seekg(0, ios::end);
  9.         int nLen = file1.tellg();
  10.         char *ss = new char[nLen+1];
  11.         memset(ss, 0, nLen);
  12.         file1.seekg(0, ios::beg);
  13.         file1.read(ss, nLen);
  14.         ss[nLen] = '\0';
  15.         m_Text = chr2wch(ss);
  16.     }
  17.     file1.close();
  18. }
复制代码
代码和unicode的方法类似,区别在于ifstream和wifstream上,嗯嗯。这两个除了名字外还有啥具体区别呢?呵呵,前面说了unicode是宽字符,而Ansi文本文件中的字符都是单字节的。其实说白了宽字符就是相对于char来说的,就是指widechar比char宽一点。使用ifstream读取文件对于Ansi文本,过程与wifstream类似,只是在m_Text赋值时有一点特别的处理:chr2wch(ss)。这是在干啥呢?chr2wch就是将char字符串转化widechar字符串,汗!~~整了半天还是要换成widestring。没办法在wince中默认是unicode的,而且MiniNote中用于显示文件内容的控件都是使用unicode的,只好转换过去。

好啦,文件读取成功啦。调用M8NoteFileReader.GetText()就能读到内容了。

讲完了读取过程接下来讲保存过程,既然打开是OpenFile方法,那保存自然就叫SaveFile了,直接进入代码吧:
  1. //保存文本文件
  2. bool M8NoteFileReader::SaveFile(TCHAR *filename, TCHAR *context)
  3. {
  4.     if (m_TextCode  == ttcUnicode)
  5.     {
  6.         SaveFileByUnicode(filename, context);
  7.     }
  8.     else if (m_TextCode == ttcAnsi)
  9.     {
  10.         SaveFileByAnsi(filename, context);
  11.     }
  12.     return true;
  13. }
复制代码
SaveFile有两个参数,第一个参数是待保存的文件位置,第二参数就是文件内容。保存过程只有一个if语句,同样的也只有unicode和ansi两种格式,这个有一点要注意,if语句里的m_TextCode是在保存方法之外设置的,MiniNote中是使用的读取时的文件编码。

进入到Unicode文件的保存方法SaveFileByUnicode里:
  1. //保存Unicode文本
  2. void M8NoteFileReader::SaveFileByUnicode(TCHAR *filename, TCHAR *context)
  3. {
  4.     wofstream outFile;
  5.     outFile.open(filename, ios::out | ios::binary);//打开并设置为写入
  6.     if (outFile.is_open())
  7.     {
  8.         outFile.seekp(0, ios::beg);//定位到文件首部
  9.         //先写入bom结构
  10.         TCHAR bom = 0xfeff;
  11.         outFile.write(&bom, 1);
  12.         //再写入文本内容
  13.         int nLen = wcslen(context);//获取文本内容的长度
  14.         outFile.write(context, nLen);//写入内容
  15.     }
  16.     outFile.close();
  17. }
复制代码
与读取时一样写入文件采用的类wofstream也是针对widechar的,保存过程多了一个步骤用于写入BOM结构,这样保存的文件就会有BOM结构,否则读出来会乱码。

返回到SaveFile方法里再看SaveFileByAnsi方法:
  1. //保存Ansi文本
  2. void M8NoteFileReader::SaveFileByAnsi(TCHAR *filename, TCHAR *context)
  3. {
  4.     ofstream outFile;
  5.     outFile.open(filename, ios::out | ios::binary);
  6.     if (outFile.is_open())
  7.     {        
  8.         char * ss = wch2chr(context);//要将文件内容转换为char字符串
  9.         int nLen = strlen(ss);
  10.         outFile.seekp(0, ios::beg);
  11.         outFile.write(ss, nLen);
  12.     }
  13.     outFile.close();
  14. }
复制代码
写入ansi文本使用的类是ofstream,对于ansi文本不需要写BOM结构。但是保存方法里传入的是TCHAR类型的字符串,而ANSI是char字符串,所以保存前要转context转换为char类型字符串,wch2chr就是干这个活的。


篇结
至此保存也说完了,看来很简单嘛,但对于不知道的人来说要研究上一小段时间才能整理出来,我就是查资料问人整了一个月才稳定下来(其实就用了其中的一点点时间,嘿嘿),最后才形成这个M8NoteFileReader。对于很多需要读写文本文件的朋友来说,这个类还是很有用的,直接拿去就能使用。哦,对了,要使用MZFC的程序咯。
其实M8NoteFileReader这个类还是写的比较差的,虽然基本的功能都已经实现,但是结构上不是很舒服,如果闲的没事重构一下,比如提取一个虚类,这样对于unicode和ansi分别继承一个子类,这样以后有了新的文本类型需要处理只要实现新的子类就好了。对于调用者来说没有什么影响。嗯嗯,以后再说吧。

本文的pdf
MiniNote1代码分析之文本存取器.pdf (, 下载次数:298)





作者:5207
出处:http://www.mini188.com
本文版权归作者所有,欢迎转载请注明出处,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
本主题由 皇帝 5207 于 2009-7-17 19:58:29 执行 设置高亮 操作
分享 转发
相信与不相信都是矛盾的.  5207宣!欢迎您来到点滴论坛
TOP
发新话题 回复该主题