Thursday, May 10, 2018

[Algorithm]String Encoding

ASCII

我们最熟悉的一种字符编码方式,用一个字节表示0-127一共128个字符,第一位始终为0。包括26个字母,阿拉伯数字,英式标点符号和一些控制字符等。


ANSI

ASCII的缺点显而易见,其他语言的字符基本都没有包括在内。为使计算机支持更多的语言,通常使用0x80~0xFF范围的两个字节来表示一个字符。那么不同的语言自然有不同的标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准,这些标准统称为ANSI编码。
ANSI和ASCII是兼容的,0x00~0x7F之间的字符,依旧是1个字节代表一个字符,和ASCII对应。而这之外的字符我们才用0x80~0xFF范围内的两个字节来表示一个字符。比如汉字找那个的'中'在简体中文中使用[0xD6, 0xD0]这两个字节存储。但是ANSI编码就一定不超过两位吗?这个不一定,对于有的语言两位肯定是不够的,所以可能有更多的位。
不同的ANSI的编码之间当然是不兼容的,因为有很多冲突的码点,在不同的编码方式下表示不同的字符。所以不同的语言是不能储存在同一个ANSI编码格式下的文本的。为了解决不同ANSI不兼容的问题,微软采用了codepage,codepage就是不同的编码方式对应的字符到Unicode的映射,即通过指定的转换表将非 Unicode 的字符编码转换为同一字符对应的系统内部使用的 Unicode 编码。这样某种程度上缓解了不兼容的问题。
编码的时候,我们可以采用变长的编码。因为有的字符需要一位就够了,比如英文字母或者阿拉伯数字。有得字符需要两位,比如汉字'中'。那么我们完全可以需要几位就给放几位,这样更加的效率。这种形式就叫做MBCS(Multi-Byte Character Set)。


Unicode

如果我们能有一个统一的集合,包括了世界上所有语言的字符,那么兼容的问题不就迎刃而解了么。Unicode的出现就解决了这个问题,这个世界上所有的文字,不管是你见过的还是没有见过的,我们都存在这个表中,这样我们就不会有任何冲突的问题,任何文字我们都可以显示在同一个编码方式的文件中。
Unicode只规定了一个二进制数所对应的字符,但是并没有规定我们改如何编码。人们可以根据不同的需求进行编码,所以UTF-8,UTF-16和UTF-32也就应运而生。

UTF-8
UTF8只是Unicode的一种实现方式,Unicode编码的方式可以不同,但码点对应的字符的关系都是一样的,UTF8只是最流行的一种编码方式,所以我们只着重介绍它。UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。其规则如下:
  • 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
  • 对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。
具体的编码规则如下表所示:

   Char. number range  |        UTF-8 octet sequence
      (hexadecimal)    |              (binary)
   --------------------+---------------------------------------------
   0000 0000-0000 007F | 0xxxxxxx
   0000 0080-0000 07FF | 110xxxxx 10xxxxxx
   0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
   0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

如何把对应的Unicode值转化成UTF8的值呢?也非常简单,根据上表来决定我们需要多少字节,然后从最低位开始填满所有的x位,填不满的补0即可。
我们举个例子,汉字中的'中'其Unicode编码为0x4E2D(01001110 00101101),可以看出我们需要三位,我们从低位到高位填满所有位之后的结果是0xE4B8AD(11100100 10111000 10101101),这就是'中'对应的UTF8编码。

BOM
BOM全称Byte Order Mark,一般放在文本文件或者字符串流的最前面,来告诉解码器输入文字是如何编码的。
比如UTF-8用的是0xEF,0xBB,0xBF,所以我们有的时候会看见""这样的乱码在首端。
对于UTF-16或者早期Unicode的编码形式UCS-2,我们存储的时候可以是高位字节在前或者低位在前。比如编码为0x4E2D,我们可以存成0x4E2D也可以存成0x2D4E。高位在前,就是Big endian,低位在前,就是Small endian。那么BOM可以告诉对应的编码形式是Big endian还是Small endian:

  • 0xFEFF代表Big endian
  • 0xFFFE代表Small endian
_T()、_Text()宏
在Visual Studio环境下开发过的应该知道的,windows的api是有两种格式的,分别针对Unicode和MBCS,比如MessageBox,一个 MessageBoxW 针对 Unicode 版的,一个是 MessageBoxA 针对 Multi-Byte 的,它们通过不同宏进行隔开,预设不同的宏会使用不同的版本。工程文件也是有相同的两种编码形式的,如果你在MBCS的工程里调用Unicode版本的API,或者相反,那显然是通不过的编译的,但是如果我们只写一个版本,工程转换编码的时候那么老的文件都不能用了,这岂不是太糟糕了?
微软当然也知道这个问题,所以给我们提供了_T()和_Text()的宏,定义如下:

#ifdef  _UNICODE
// ... 省略其它代码
#define __T(x)      L ## x
// ... 省略其它代码
#else   /* ndef _UNICODE */
// ... 省略其它代码
#define __T(x)      x
// ... 省略其它代码
#endif  /* _UNICODE */

如果工程文件的编码方式为Unicode,我们在前面加上L转化为wchar,也就是Unicode编码的字符串。否则我们按MBCS处理。这样问题就迎刃而解了。

No comments:

Post a Comment