分字(汉字编码)

关于使用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函数中指定文件编码。除此之外,结果也可以在控制台和文件中同时输出。

参考文献

[1] 李老师的讲义-Chinese-encoding

上一篇:Ubuntu Server 18.04 配置 rsync(xinetd)


下一篇:ascll 和gbk,utf-8的简介