Character Set 三两句
最近在做一些跨平台的东东,这个encoding确实让我琢磨了一会儿,以前只是听说过没当回事,现在必须要认真理解以前那些耳熟能详的概念。
- ASCII码已经是古董了。它是一种7-bit的编码,这个就不用我多说了。(历史上是ISO-646-US标准)
- 然后就是ISO-8859系列。这是一种8-bit的编码(因为是单字节编码,所以又叫Single Byte Character Set/SBCS),添加了ASCII不能表示的控制字符。
- MBCS/DBCS是多字节编码,是为了弥补上面的编码不能表示东方或者其他非拉丁字符的缺陷。比如说Shift-JIS就是日文的字符集。
- Unicode:为了实现“天下一统”,Unicode Consortium提出了Unicode 标准。它的目标就是统一世界上的字符集。Unicode的特点就是它只定义了code point(每一个unicode编码就是一个code point)的语义,没有定义它本身的编码形式以及对应字符的字形(glyph)。举个简单的例子:U+05D0就是一个犹太文的字母,但是这个字符在计算机中编码后会是什么样的字节串,不同的编码(encodings)会产生不同的结果。比如说UTF-16就是Windows内部采用的Unicode的编码方式,这个字母就会表示成 05 D0 (占用两个字节)。但是如果采用Linux下的UTF-8编码方式,这个字母就是 D7 90 (也是两个字节,但是值完全不一样)。下面这个表格说明了UTF-8根据code point所在不同的range所采用的编码方式。
| Code range (hexadecimal) | Scalar value (binary) | UTF-8(binary / hexadecimal) | Notes |
|---|---|---|---|
| 000000–00007F128 codes | 00000000 00000000 0zzzzzzz | 0zzzzzzz(00-7F) | ASCII equivalence range; byte begins with zero |
| seven z | seven z | ||
| 000080–0007FF1920 codes | 00000000 00000yyy yyzzzzzz | 110yyyyy(C2-DF) 10zzzzzz(80-BF) | first byte begins with 110, the following byte begins with 10. |
| three y; two y, six z | five y; six z | ||
| 000800–00FFFF63488 codes | 00000000 xxxxyyyy yyzzzzzz | 1110xxxx(E0-EF) 10yyyyyy 10zzzzzz | first byte begins with 1110, the following bytes begin with 10. |
| four x,four y; two y,six z | four x; six y; six z | ||
| 010000–10FFFF1048576 codes | 000wwwxx xxxxyyyy yyzzzzzz | 11110www(F0-F4) 10xxxxxx 10yyyyyy 10zzzzzz | First byte begins with 11110, the following bytes begin with 10 |
| three w, two x; four x, four y; two y, six z | three w; six x; six y; six z |
Unicode下两种主要的编码方式UTF-16和UTF-8各自在不同的平台上发挥其本领。UTF-8的设计很象Huffman编码,也就是变长的编码(从表格中可以看出来)。编码最短的是拉丁字母(1个字节),一共128个code points,而且这个字节的最高位一定是0。这样跟ASCII保持了很好的兼容性,因为ASCII编码的字符序列也是UTF-8的字符序列。变长的编码也节省了空间。
Windows长期以来都使用UCS-2编码。这是一种双字节的定长编码。但是双字节不能表示Unicode中code point在U+FFFF以上的字符(U+FFFF一下的所有字符构成了Basic Multilingual Plane /BMP),所以从XP开始,UTF-16便开始在Windows内部使用。UTF-16的编码值其实几乎就是Unicode 的值,大多数字符还是在U+FFFF以下(在U+FFFF以上的字符UTF-16采用了surrogate pair方式进行特殊编码,可以参考http://en.wikipedia.org/wiki/UTF-16)。为了更好的表示UTF-16编码,C/C++标准采用了wchar_t这个类型,也就是一个双字节的类型;同时Java里面的char本身就是一个double byte的类型。
UTF-32也是一种编码,但是它是一种定长的编码,它的编码值就和Unicode中的code point一一对应。UTF-32可以作为UTF-8和UTF-16之间转换的媒介。
对于Windows程序员来说,TCHAR提供了一定便利。TCHAR是一个宏定义,它可以是Unicode(UTF-16)可以是MBCS/DBCS,也可以是ANSI(任何基于单字节的编码)。这样写程序的时候就尽量使用TCHAR而不用关心底层的编码实现。比如说很多地方就可以使用LPCTSTR (Long Pointer to a Const TCHAR STRing)类型。TCHAR具体代表什么编码类型需要在程序中用宏定义。
Linux就没有这么麻烦了,因为Linux内部使用的就是UTF-8编码,所以字节流就足够了。但是跨平台的时候就比较麻烦了。
其实所有的编码都可以表示为字节流。但是,当说到字符串的时候就不太好办了。比如”abc”用UTF-8和UTF-16表示出来是不一样的字节流。UTF-8是2个字节长,而UTF-16是4个字节长。所以为每种编码引入对应的基本数据类型给字符串操作带来了方便。UTF-8的基本编码单位是单字节,所以char[]足以胜任;UTF-16的编码单位是双字节,所以wchar_t[]正好合适对UTF-16编码单位的操作。这样看来,实际UTF-8/16里面的8和16其实是编码单位的大小,而并不是说每个字符只有8个bit或者16个bit(在UTF-8中,一个字符最多可以编码为4个字节,UTF-16中同样也有两个双字节表示的字符)。所以在传输这些编码的时候,作为字节流没有任何问题;但是如果要解释和处理这些编码的时候,使用相应的数据类型方便得多。
posted in 编程珠玑 | 0 Comments