关于使用GBK和UTF8进行汉字编码的学习总结。
1 原理总结
1.1 GBK(GB)
GBK编码规则简述:
- 使用1-2个字节表示一个字符,从左向右一个字节一个字节识别。
- 若当前字节首位为0,则该字符用1个字节表示(ASCII字符)。
- 若当前字节首位为1,则该字符用2个字节表示(汉字)。
列表如下:
1字节字符(ASCII字符) | 2字节字符(汉字) |
---|---|
0XXXXXXX | 1XXXXXXX XXXXXXXX |
其中"X"代表0或1。
1.2 UTF8
UTF8编码规则简述:
- 使用1-6个字节表示一个字符,从左向右一个字节一个字节识别。
- 若当前字节首位为0,则该字符用1个字节表示(ASCII字符,同GBK)。
- 当字节数大于2时(此时表示汉字),假设该字符有n个字节,该字符的表示与n的关系如下:
- 首字节:前n位是1,n+1位是0,后面任意。
- 其余n-1个字节,前两位必须是10,后面任意。
注意汉字的编码前面至少有2个1(有几个1就表示该汉字用几个字节表示)。
列表如下:
1字节字符(ASCII字符) | 2字节字符(汉字) | 3字节字符(汉字) | 4字节字符(汉字) | 5字节字符(汉字) | 6字节字符(汉字) |
---|---|---|---|---|---|
0XXXXXXX | 110XXXXX 10XXXXXX |
1110XXXX 10XXXXXX 10XXXXXX |
11110XXX 10XXXXXX 10XXXXXX 10XXXXXX |
111110XX 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX |
1111110X 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX 10XXXXXX |
2 代码实现
2.1 数据
因为重在试验,数据非常简单,是自己写的如下一段话。
Google是拥有超过10亿用户的公司 也是全球最大的搜索引擎公司
分别在记事本(windows)中用utf8和gbk(ANSI)编码保存。
要求每个字符用空格隔开,并统计字符个数,包括汉字和数字、英文字符等ASCII字符。
2.2 C++实现
C++的一个字符(char类型)只有8位,即一个字节,所以是不能直接表示一个汉字的。需要根据相应编码的原理来识别汉字。把读到的内容转换为字符数组后,就根据具体的编码规则一个字符一个字符的识别汉字(或ASCII字符)。具体就是判断当前要识别的汉字(或ASCII字符)由几个字节(即C++字符)组成,把这几个C++字符保存到一个字符串中再输出,就可以输出一个特定编码的汉字(或ASCII字符)了。
2.2.1 读文件
因为需要按位操作,最好用二进制的方式读文件。
ifstream f_in("path/to/utf-8_input.txt",ios::in|ios::binary);//以二进制形式读文件
2.2.2 获取文件长度(字节数)
f_in.seekg(0,ios::end);//把指针移到文件末尾
f_len=f_in.tellg();//获取文件长度
f_in.seekg(0,ios::beg);//把指针移到文件开头
2.2.3 把文件内容保存到字符串中,方便操作。
char *buffer=new char[f_len];//开辟文件长度大小的空间
f_in.read(buffer,f_len);//按字节读文件,全部存到buffer中
string s=buffer;//buffer的内容存到字符串,方便操作
2.2.4 进行字符识别
这里可以使用C++自带的bitset进行操作,非常方便。bitset类似一个bool类型的数组,每个C++字符(尽管它可能没有意义)都可以转换成一个bitset对象,然后就可以判断以它为首字节的汉字(或ASCII字符)由几个字节组成了。需要注意的是,对于bitset对象,首位在最高位,如果用byte表示一个C++字符转换来的二进制数组,则byte[7]是首位。
识别gbk字符的实现如下
for(int i=0; i<f_len;)
{
bitset<8> byte(s[i]);//转二进制
if(s[i]==' ')//遇到空格跳过(utf-8,gbk,ascii码空格均为0x20)
{
i=i+1;
continue;
}
else if(byte[7]==0) c_len=1;//首位是0,1字节
else if(byte[7]==1) c_len=2;//首位是1,2字节
string c=s.substr(i,c_len);
cout<<c;
f_out<<c;
if(i+c_len<f_len)//最后一个字符后面没有空格
{
cout<<' ';
f_out<<' ';
}
num++;
i=i+c_len;
}
识别utf8字符的实现如下
for(int i=0; i<f_len;)
{
bitset<8> byte(s[i]);//单字节转二进制
if(s[i]==' ')//遇到空格跳过(utf-8,gbk,ascii码空格均为0x20)
{
i=i+1;
continue;
}
else if(byte[7]==0) c_len=1;//0xxxxxxx,1字节
else if(byte[5]==0) c_len=2;//110xxxxx,2字节
else if(byte[4]==0) c_len=3;//11110xxx,3字节
else if(byte[3]==0) c_len=4;//111110xx,4字节
else if(byte[2]==0) c_len=5;//1111110x,5字节
else if(byte[1]==0) c_len=6;//11111110,6字节
string c=s.substr(i,c_len);
f_out<<c;
if(i+c_len<f_len) f_out<<' ';//最后一个字符后面没有空格
num++;
i=i+c_len;
}
遗憾的是,由于控制台只支持gbk编码,打印utf8字符后会乱码;因此utf8编码的结果只能输出到文件中,并且不能在utf8文件中输出汉字(使用键盘打字,用ofstream输出到文件中),因为C++字符串中的汉字也是gbk编码的,输出到utf8文件中会乱码。
2.3 python实现
python可以直接识别GBK和UTF8编码的字符,先用字符串的split函数转成成字符串列表,再对列表中每一个字符串进行遍历即可。注意用在open函数中指定文件编码。除此之外,结果也可以在控制台和文件中同时输出。