由谈退格键的实现来学习字符编码
我曾以为老师的话是真的,我曾以为老师会为自己说出的话负责,但事实证明很多时候是照本宣科。
这次在公司做Fcitx输入法时,想到退格删除的字节数的不同,即退格键一按到底删除的是一个字节还是两个字节或者多个。在测试中我发现,中文汉字占了三个字节,竟然占了三个字节,老师不是经常告诉我们汉字是占两个字节,但现在怎么占了三个字节,是老师的错还是程序的错,亦或本都没错,只是没有探其根本罢了。
说汉字占两个字节是从以Unicode的编码方式UCS-2来说的,但实际占三个字节,是从Unicode的实现方式UTF-8来说的。
先介绍下Unicode字符集吧。
什么是字符?什么是字符集?
字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。
字符集是多个字符的集合,字符集种类较多,每个字符集合包含的字符个数不同,常见字符集名称:ASCII字符集、Unicode字符集、GB2312字符集、BIG5字符集、GB18030字符集等。
多年来,许多人一直将文本串作为一系列单字节字符来进行编码,并在结尾处放一个零。对于我们来说,很习惯调用strlen函数来返回以0结尾的单字节字符数组中字符数目。这是我们平时所说的ASCII字符,即单字节字符,一个字符占一个字节,即八位,所以它提供的符号最多不能超过1+28-1=256个字符,但明显单字节是根本不够用的,为此出现了双字节字符集(DBCS)。
Unicode的编码和实现。
大概来说,Unicode编码系统可分为编码方式和实现方式两个层次。
编码方式
Unicode是国际组织制定的可以容纳世界上所以文字和符号的字符编码方案。Unicode用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符,或者说有1114112个码位。码位就是可以分配给字符的数字。UTF-8、UTF-16、UTF-32都是将数字转换到程序数据的编码方案。
通用字符集(Universal Character set,UCS)是由ISO制定的ISO 10646(或称ISO/IEC 10646)标准所定义的标准字符集。UCS-2用两个字节编码,UCS-4用4个字节编码。
实现方式
在Unicode中:汉字的“字”对应的数字是23383。在Unicode中,有许多方式将数字23383表示成程序中的数据,包括UTF-8、UTF-16、UTF-32。UTF是“UCS Transformation Format”的缩写,Unicode字符集转换格式,即怎样将Unicode定义的数字转换成程序数据。例如,“汉字”对应的数字是0x6c49和0x5b57,而编码的程序数据是:
BYTE date_utf8[]={0xE6,0xB1,0x89,0xE5,0xAD,0x97};
可见,一个汉字以UTF-8编码是占三个字节。
UTF-8
UTF-8以字节为单位对Unicode进行编码。从Unicode到UTF-8的编码方式如下:
Unicode编码(16进制) UTF-8字节流(二进制)
000000-00007F 0xxxxxxx
000080-0007FF 110xxxxx 10xxxxxx
000800-00FFFF 1110xxxx 10xxxxxx 10xxxxxx
010000-10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
UTF-8的特点是对不同范围的字符使用不同长度的编码,所以UTF-8兼容ASCII编码。UTF-8编码的最大长度是4个字节。从上表可以看出,4个字模板有21个x,即可以容纳21位二进制数字。Unicode的最大码位0x10FFFF也只有21位。
从Unicode编码的UTF-8实现方式比较好理解:
例如,汉字的“汉”字的Unicode编码是0x6C49。0x6C49是在0x800-0xFFFF之间,则对应用UTF-8来实现需要点三个字节,1110xxxx 10xxxxxx 10xxxxxx。
0x6C49的二进制是0110 1100 0100 1001,用这个比特流依次代替模板中的x,即用0110 1100 0100 1001从低位到高位替代1110xxxx 10xxxxxx 10xxxxxx的x位,不足时用0代替。
0110 1100 0100 1001
1110xxxx 10xxxxxx 10xxxxxx
11100110 10110001 10001001
现在谈谈针对是UTF-8编码方式而言退格键的实现。
void utf8_backspace(char *text) { int n = strlen(text); char *last_byte = text + n - 1; if(!((*last_byte) & 0x80)) { text[n-1] = '\0'; } else if(((*(last_byte-1)) & 0xc0) == 0xc0) { text[n-2] = '\0'; text[n-1] = '\0'; } else if(((*(last_byte-2)) & 0xe0) == 0xe0) { text[n-3] = '\0'; text[n-2] = '\0'; text[n-1] = '\0'; } else if(((*(last_byte-3)) & 0xf0) == 0xf0) { text[n-4] = '\0'; text[n-3] = '\0'; text[n-2] = '\0'; text[n-1] = '\0'; } else { printf("[%s]:%d,uft8 backspace error\n",_FILE_,_LINE_); } }
UTF-8理论上最多可以是6个字节,这里只考虑4个字节,因为大部分4个字节就已经足够。
这个实现退格键的前提是你的字符是经过UTF-8,如果是其它的,如GB2312等须先进行编码转换成UTF-8才能用。
Unicode能显示20901个汉字,范围是从\u4e00到\u9fa5,下面程序是输出Unicode编码下的所有汉字。
//C# code char minHZUnicode = '\u4e00'; char maxHZUnicode = '\u9fa5'; for (char c = minHZUnicode; c <= maxHZUnicode; c++) { Console.Write(c); }