关于Unicode和字符集的最基础的知识

ASCII 码

  7 位(00~7F)。32 ~ 127 表示字符。32 是空格,32 以下是控制字符(不可见)。第8位没有被使用。全世界非常多人同时对这个位的含义发展了不同的用处。比如 IBM PC 中的 OEM 字符集。最后就 128 位以下的用处达成共识,制定了 ASCII 标准。而 128 位以上的可能有不同的解释,这些不同的解释就叫做 code pages。甚至有用于在同一台计算机上解释多种语言的 code page。

  同时,在亚洲发生了更加疯狂的事情。亚洲语言的字符集通常数以千计, 8 位已不足以表达,这通常用一种非常凌乱的,叫做 DBCS(双字节字符集,double byte character set) 的系统来解决。这种系统中,有些字符占用 1 字节,有些 2 字节。这样一来,在字符串中向前解析非常容易,而倒退却非常麻烦。程式员们被建议,不要使用 s++ 或 s-- 来前进和后退,而使用一些函数,比如 视窗系统 的 AnsiNext 和AnsiPrev。因为这些函数知道是怎么回事。

  这些不同的假设(code page)在单个的机器上没有问题。而随着 Internet 的发展,字符串要从一个机器上移到另一个机器上,这就产生了问题。于是,Unicode 出现了。

Unicode

  Unicode 是个勇敢的成就。他把在这个星球上的每一个合理的文字系统整合成了一个单一的字符集。非常多人还存在这样的误解: Unicode 仅仅是 16 位的这么简单,每个字符占 16 位,所以一共有 65536 个可能的字符。然而,这是错误的。不过不要紧,因为这是大部分人都会犯的一个普遍的错误。

  实际上,Unicode 理解字符的方式是截然不同的,而这是我们必须了解的。到目前为止,我们都原来认为:一个字符对应到一些在磁盘上或内存中储存的位(bits), 如: A -> 0100 0001。而在 Unicode 中, 一个字符实际上对应一种叫做 code point 的东西。比如 A 这个字符,是抽象的(原文:platonic,柏拉图式的,最佳的)一个概念。无论是 Times New Roman 或 Helvetica 或其他的什么字体中,都代表同一个字符。不过他和小写的字母 a 不同。不过在其他的语言,比如希伯莱语(Hebrew) 或德语(German), 阿拉伯语(Arabian) 中,同一个字母的不同的字形代表的含义是否相同,是有争议的。经过长时间的争论,这些也终于被确定了。

  每一个字母表中的每一个抽象的字母,都被赋予了一个数字,比如 U+0645。这个叫做 code point。U+ 表示: Unicode, 数字是 16 进制的。你能通过 charmap 命令来查看所有这些编码。(视窗系统 2000/XP 中)或访问 Unicode 的网站(http://www.unicode.org)Unicode 中 code point 的数字的大小是没有限制的,而且也早就超过了 65535。所以不是每个字符都能存储在两个字节中。那么,一个字符串 "Hello", 在 Unicode 中会表示成 5 个 code points :

程式代码
U+0048 U+0065 U+006C U+006C U+006F

只不过是一些数字。但我们目前还没有提到怎么在磁盘或 Email 中表示这些信息,这就是我们下面要提到的编码(Encoding)干的事情。

Encodings (编码)

  最初的 Unicode Encoding, 使用两个字节表示一个字符。那么 "Hello" 表示为:
00 48 00 65 00 6C 00 6C 00 6F
实际上,更有一种表示方式:
48 00 65 00 6C 00 6C 00 6F 00
到底高位字节在前还是低位字节在前面,是两种不同的模式。这要看特定的 CPU 在何种模式下工作的更快。 所以这两种都有。这就有了两种不同的 Unicode 表示方式了,为了区分,人们又采用了一种奇异的方式:在每一个 Unicode 字符串的前面,加上 FEFF (这称为 Unicode 字节顺序标志,Unicode Byte Order Mark).如果你交换高位和低位次序,那么会加上一个 FFFE. 这样,读这个字符串的人才知道要对每两个相邻的字节进行交换。但在最初的时候,并不是每一个 Unicode 字符串都有这个标志的。

  这看起来非常不错。可程式员们开始抱怨了,“看看那些零!”。因为有些是美国人,他们使用英语。而英语中非常少需要使用 U+00FF 以上的字符, 有些人无法忍受采用双倍的存储空间来存储每个字符。基于这些原因,非常多人决定忽视 Unicode, 而同时,事情变得更糟了。

  然后人们制定了 UTF-8. UTF-8 是用于保存 Unicode code points 的另一套系统。每一个 U+ 数字,在内存中占用 8 bit. 在 UTF-8 中,所有一个 0~127 的 code point 占用一个字节。只有 128 及更大的才占用 2, 3, 直到 6 个字节。具体如下图所示:

16进制的最小的数   16进制的最大的数   内存中的字节序列
-------------------------------------------------------------------------------------------------------------------------------
00000000       0000007F       0vvvvvvv
00000080       000007FF       110vvvvv 10vvvvvv
00000800       0000FFFF       1110vvvv 10vvvvvv 10vvvvvv
00010000       001FFFFF       11110vvv 10vvvvvv 10vvvvvv 10vvvvvv
00200000       03FFFFFF       111110vv 10vvvvvv 10vvvvvv 10vvvvvv 10vvvvvv
04000000       7FFFFFFF       1111110v 10vvvvvv 10vvvvvv 10vvvvvv 10vvvvvv 10vvvvvv

  这看起来非常不错,其中的英文字符和 ASCII 中相同。所以美国人根本没意识到有什么错误。只有世界上的其他国家需要使用高位的字节。特别的,"Hello" 这个字符串,Unicode code point 为 U+0048 U+0065 U+006C U+006C U+006F, 会被存储为 48 65 6C 6C 6F。和 ASCII, ANSI, 及在这个星球上的所有一个 OEM 的字符集中表示的含义都相同。目前,如果你需要表示重音的字符,或希腊语,你需要使用多个字节来表示一个 code point. 但美国人不会介意这些。(UTF-8 更有一个好处就是,老的字符串处理程式使用一个为 0 的字节来表示 null-terminator, 不会截断字符串)

  到目前为止已介绍了三种 Unicode 的表示方法:

  传统的双字节表示方法, 称为 UCS-2(因为有 2 个字节) 或 UTF-16(因为有 16 个位)而且你还要搞清晰是高位在前的,还是高位在后的 UCS-2.

  更有一种就是新的 UTF-8. 如果你的程式只使用英文的话,他仍然会工作正常。

  实际上更有一堆的其他办法对 Unicode 进行编码:有 UTF-7,这种编码方式大部分和 UTF-8 相同,但确保高位一定为 0. 所以如果你必须通过某种 Email 系统传送 Unicode,这些系统认为 7 位足够了,那使用 UTF-7 会正常。更有 UCS-4, 储存每一个 code point 为 4 个字节。他的好处是每一个字符都保存为同样长的。但非常明显,缺点是浪费太多存储空间了。

  所以,目前你思考问题要把每一个字符想象成抽象的一个 unicode code point. 而他们同样能使用所有旧的方式编码。举例来说,你能把 Unicode 字符串 Hello (U+0048 U+0065 U+006C U+006C U+006F) 编码(encode)为ASCII, 或古老的 OEM 希腊语编码,或希柏莱 ANSI 编码,等等。而有些字符串不能显示!也就是说,如果你要表示一个在某个编码中没有对应的 Unicode code point, 通常会显示为一个 ? 或一个白色的小方框。

  英文常用的一些编码有, 视窗系统-1252(视窗系统 9x 标准 for 西欧语言)及 ISO-8859-1, aka Latin-1(对所有西欧语言也有效)如果用这些编码来尝试存储俄文字符,你会得到一堆的 ?UTF 7, 8, 16 及 32 都有一个好处,能够正确的存储所有的 code point.

最简单,也是最重要的几个概念

  一个字符串不指定他使用什么编码是没有意义的。再也不要假定, “纯”文本(plain text) 是 ASCII.没有 “纯文本” 这个东西。

  如果你有一个字符串,在内存中,在文件中,或在 Email 消息里,你必须知道他的编码是什么。否则你无法正确的解释或显示给用户。所有的诸如 “我的网页不能正常显示了”,或 ”Email 消息不能正常显示了“ 之类的愚蠢问题, 都是因为, 没有告诉你到底是使用的那种编码,UTF-8 还是 ASCII 还是 ISO 8859-1 或 视窗系统 1252 ?? 那么自然无法正常的解释和显示,甚至不知道字符串该在哪里结束。

  那么怎么保留这样的编码标志,来表示字符串的编码? 有一些基本的办法。比如对于 Email 来说,在表单的 header 中加上:

Content-Type:text/plain;charset="UTF-8"

对于 Web 页面来说,原来的做法是, Web 服务器随着 web 页面本身一起,发送一个类似于 Content-Type 的 http header.(不是在 HTML 里面,而是作为一个 response header 在 HTML 页之前发送)

  这样做有一个问题。如果你的 Web 服务器同时有多个站点,站点由多个不同的人用不同的语言研发的程式混在一起。那么 Web 服务器将无从得知,每一个文件是用什么编码方式写的。这样也就无法发送正确的 Content-Type header.如果你能够在每一个 HTML 文件中记录 Content-Type 信息,那么就非常方便了。可这念头似乎也非常疯狂,因为你还没有知道用什么编码方式去读取这个文件,又怎么能读出编码信息呢?幸好,几乎每一种编码中,对 32~127 的字符都解释的相同。所以你能在每一个 html 文件中这么写:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">

  不过要注意, 这个 meta 标签必须放在 head 中靠前面的位置才能确保不会出问题。 因为 Web 服务器读到这里的时候,就会停止解析,然后用读到的这个编码方式重新解析页面。那么,作为 Web 浏览器来说,如果没有在 meta 标签中或 http headers 中发现 Content-Type, 会怎么样呢?IE 是这么做的:先尝试去猜,根据特定的字节出目前各种语言的典型的编码中的频率。如果编码设定不正常,用户能通过 View|Encoding 菜单来尝试不同的编码方式。(当然,不是每个人都知道该这样做)

  在 VB, COM, 视窗系统 NT/2000/XP 中,默认的字符串类型是 UCS-2(2字节)的。在 C++ 代码中, 我们能定义字符串为 wchar_t(wide char),同时用 wcs 系列的函数代替 str 系列的函数。如 wcscat, wcslen, 而不是 strcat, strlen.在 C 代码中,要创建 UCS-2 字符串的话,只要在前面加一个 "L", 如 L"Hello"对于 Web 页面,最佳统一为使用 UTF-8 编码。 这个编码已被各种 web 浏览器支持了非常多年了。

 

 

 

 

UTF-8
The default encoding used in XML documents, unless an encoding declaration, byte order mark, or external metadata specifies otherwise; a variable-width encoding of Unicode that uses one to six bytes per character. UTF-8 is designed such that all ASCII documents are legal UTF-8 documents, which is not true for other character sets, such as UTF-16 and Latin-1. This character set is the best encoding choice if your XML documents contain limited Chinese, Japanese, or Korean.

UTF-16
A two-byte encoding of Unicode in which all Unicode characters defined in Unicode 3.0 and earlier (including the ASCII characters) occupy exactly two bytes. However, characters from planes 1 through 14, added in Unicode 3.1 and later, are encoded using surrogate pairs of 4 bytes each. This encoding is the best choice if your XML documents contain substantial amounts of Chinese, Japanese, or Korean.

ISO-10646-UCS-2
The Basic Multilingual Plane of Unicode, i.e., plane 0. This character set is the same as UTF-16, except that it does not allow surrogate pairs to represent characters with code points beyond 65,535. The difference is only significant in Unicode 3.1 and later. Each Unicode character is represented as exactly one two-byte, unsigned integer. Determining endianness requires a byte-order mark at the beginning of the file.

ISO-10646-UCS-4
A four-byte encoding of Unicode in which each Unicode character is represented as exactly one four-byte, unsigned integer. Determining endianness requires a byte-order mark at the beginning of the file.

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值