使用C++读取UTF8及GBK系列的文本方法及原理
作者:jostree 转载请注明出处 http://www.cnblogs.com/jostree/p/4374404.html
1.读取UTF-8编码文本原理
首先了解UTF-8的编码方式,UTF-8采用可变长编码的方式,一个字符可占1字节-6字节,其中每个字符所占的字节数由字符开始的1的个数确定,具体的编码方式如下:
U-00000000 - U-0000007F: 0xxxxxxx
U-00000080 - U-000007FF: 110xxxxx 10xxxxxx
U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
因此,对于每个字节如果起始位为“0”则说明,该字符占有1字节。
如果起始位为“10”则说明该字节不是字符的起始字节。
如果起始为为$n$个“1”+1个“0”,则说明改字符占有$n$个字节。其中$1 \leq n \leq 6$。
因此对于UTF-8的编码,我们只需要每次计算每个字符开始字节的1的个数,就可以确定这个字符的长度。
2.读取GBK系列文本原理
对于ASCII、GB2312、GBK到GB18030编码方法是向下兼容的 ,即同一个字符在这些方案中总是有相同的编码,后面的标准支持更多的字符。
在这些编码中,英文和中文可以统一地处理。区分中文编码的方法是高字节的最高位不为0。
因此我们只需处理好GB18130,就可以处理与他兼容的所有编码,对于GB18130使用双字节变长编码。
单字节部分从 0x0~0x7F 与 ASCII 编码兼容。双字节部分,首字节从 0x81~0xFE,尾字节从 0x40~0x7E以及 0x80~0xFE,与GBK标准基本兼容。
因此只需检测首字节是否小于0x81即可确定其为单字节编码还是双字节编码。
3.C++代码实现
对于一个语言处理系统,读取不同编码的文本应该是最基础的需求,文本的编码方式应该对系统其他调用者透明,只需每次获取一个字符即可,而不需要关注这个文本的编码方式。从而我们定义了抽象类Text,及其接口ReadOneChar,并使两个文本类GbkText和UtfText继承这个抽象类,当系统需要读取更多种编码的文件时,只需要定义新的类然后继承该抽象类即可,并不需要更改调用该类的代码。从而获得更好的扩展性。
更好的方式是使用简单工厂模式,使不同的文本编码格式对于调用类完全透明,简单工厂模式详解请参看:C++实现设计模式之 — 简单工厂模式
其中Text抽象类的定义如下:
1 #ifndef TEXT_H 2 #define TEXT_H 3 #include <iostream> 4 #include <fstream> 5 using namespace std; 6 class Text 7 { 8 protected: 9 char * m_binaryStr; 10 size_t m_length; 11 size_t m_index; 12 public: 13 Text(string path); 14 void SetIndex(size_t index); 15 virtual bool ReadOneChar(string &oneChar) = 0; 16 size_t Size(); 17 virtual ~Text(); 18 }; 19 #endif
Text抽象类的实现如下:
1 #include "Text.h" 2 using namespace std; 3 Text::Text(string path):m_index(0) 4 { 5 filebuf *pbuf; 6 ifstream filestr; 7 // 采用二进制打开 8 filestr.open(path.c_str(), ios::binary); 9 if(!filestr) 10 { 11 cerr<<path<<" Load text error."<<endl; 12 return; 13 } 14 // 获取filestr对应buffer对象的指针 15 pbuf=filestr.rdbuf(); 16 // 调用buffer对象方法获取文件大小 17 m_length=(int)pbuf->pubseekoff(0,ios::end,ios::in); 18 pbuf->pubseekpos(0,ios::in); 19 // 分配内存空间 20 m_binaryStr = new char[m_length+1]; 21 // 获取文件内容 22 pbuf->sgetn(m_binaryStr,m_length); 23 //关闭文件 24 filestr.close(); 25 } 26 27 void Text::SetIndex(size_t index) 28 { 29 m_index = index; 30 } 31 32 size_t Text::Size() 33 { 34 return m_length; 35 } 36 37 Text::~Text() 38 { 39 delete [] m_binaryStr; 40 }
GBKText类的定义如下:
#ifndef GBKTEXT_H #define GBKTEXT_H #include <iostream> #include <string> #include "Text.h" using namespace std; class GbkText:public Text { public: GbkText(string path); ~GbkText(void); bool ReadOneChar(string & oneChar); }; #endif
GBKText类的实现如下:
1 #include "GbkText.h" 2 GbkText::GbkText(string path):Text(path){} 3 GbkText::~GbkText(void) {} 4 bool GbkText::ReadOneChar(string & oneChar) 5 { 6 // return true 表示读取成功, 7 // return false 表示已经读取到流末尾 8 if(m_length == m_index) 9 return false; 10 if((unsigned char)m_binaryStr[m_index] < 0x81) 11 { 12 oneChar = m_binaryStr[m_index]; 13 m_index++; 14 } 15 else 16 { 17 oneChar = string(m_binaryStr, 2); 18 m_index += 2; 19 } 20 return true; 21 }
UtfText类的定义如下:
1 #ifndef UTFTEXT_H 2 #define UTFTEXT_H 3 #include <iostream> 4 #include <string> 5 #include "Text.h" 6 using namespace std; 7 class UtfText:public Text 8 { 9 public: 10 UtfText(string path); 11 ~UtfText(void); 12 bool ReadOneChar(string & oneChar); 13 private: 14 size_t get_utf8_char_len(const char & byte); 15 }; 16 #endif
UtfText类的实现如下:
1 #include "UtfText.h" 2 UtfText::UtfText(string path):Text(path){} 3 UtfText::~UtfText(void) {} 4 bool UtfText::ReadOneChar(string & oneChar) 5 { 6 // return true 表示读取成功, 7 // return false 表示已经读取到流末尾 8 if(m_length == m_index) 9 return false; 10 size_t utf8_char_len = get_utf8_char_len(m_binaryStr[m_index]); 11 if( 0 == utf8_char_len ) 12 { 13 oneChar = ""; 14 m_index++; 15 return true; 16 } 17 size_t next_idx = m_index + utf8_char_len; 18 if( m_length < next_idx ) 19 { 20 //cerr << "Get utf8 first byte out of input src string." << endl; 21 next_idx = m_length; 22 } 23 //输出UTF-8的一个字符 24 oneChar = string(m_binaryStr + m_index, next_idx - m_index); 25 //重置偏移量 26 m_index = next_idx; 27 return true; 28 } 29 30 31 size_t UtfText::get_utf8_char_len(const char & byte) 32 { 33 // return 0 表示错误 34 // return 1-6 表示正确值 35 // 不会 return 其他值 36 37 //UTF8 编码格式: 38 // U-00000000 - U-0000007F: 0xxxxxxx 39 // U-00000080 - U-000007FF: 110xxxxx 10xxxxxx 40 // U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx 41 // U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 42 // U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 43 // U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 44 45 size_t len = 0; 46 unsigned char mask = 0x80; 47 while( byte & mask ) 48 { 49 len++; 50 if( len > 6 ) 51 { 52 //cerr << "The mask get len is over 6." << endl; 53 return 0; 54 } 55 mask >>= 1; 56 } 57 if( 0 == len) 58 { 59 return 1; 60 } 61 return len; 62 }
工厂类TextFactory的类定义如下:
1 #ifndef TEXTFACTORY_H 2 #define TEXTFACTORY_H 3 #include <iostream> 4 #include "Text.h" 5 #include "UtfText.h" 6 #include "GbkText.h" 7 using namespace std; 8 class TextFactory 9 { 10 public: 11 static Text * CreateText(string textCode, string path); 12 }; 13 #endif
工厂类的实现如下:
1 #include "TextFactory.h" 2 #include "Text.h" 3 Text * TextFactory::CreateText(string textCode, string path) 4 { 5 if( (textCode == "utf-8") 6 || (textCode == "UTF-8") 7 || (textCode == "ISO-8859-2") 8 || (textCode == "ascii") 9 || (textCode == "ASCII") 10 || (textCode == "TIS-620") 11 || (textCode == "ISO-8859-5") 12 || (textCode == "ISO-8859-7") ) 13 { 14 return new UtfText(path); 15 } 16 else if((textCode == "windows-1252") 17 || (textCode == "Big5") 18 || (textCode == "EUC-KR") 19 || (textCode == "GB2312") 20 || (textCode == "ISO-2022-CN") 21 || (textCode == "HZ-GB-2312") 22 || (textCode == "gb18030")) 23 { 24 return new GbkText(path); 25 } 26 return NULL; 27 }
测试的Main函数如下:
1 #include <stdio.h> 2 #include <string.h> 3 #include <iostream> 4 #include "Text.h" 5 #include "TextFactory.h" 6 #include "CodeDetector.h" 7 using namespace std; 8 int main(int argc, char *argv[]) 9 { 10 string path ="日文"; 11 string code ="utf-8"; 12 Text * t = TextFactory::CreateText(code, path); 13 string s; 14 while(t->ReadOneChar(s)) 15 { 16 cout<<s; 17 } 18 delete t; 19 }
编译运行后即可在控制台输出正确的文本。