字符集——从ASCII到Unicode

时间:2022-11-16 12:40:12

最近被文件读写的乱码搞郁闷了,现在来研究一下。

 

首先了解一下字符编码的历史。

ASCII,这无需多说(American Standard Code for Information Interchange)美国信息交换标准代码,是一套基于拉丁字母的电脑编码系统,至今为止共定义了128个字符,可以显示现代英语。但是随着万维网的发展,多语言的处理使得该标准不再适用,逐渐被之后的Unicode所取代。

Unicode,是计算机科学领域里的一项业界标准,随着通用字符集的标准而发展,至今已经收录了超过十万个字符。Unicode备受认可,并广泛地应用于计算机软件的国际化与本地化过程中。值得一提的是(没有具体验证),XML和Java采用的都是Unicode编码。统一码联盟在1991年首次发布了The Unicode Standard,并逐渐发展成现在的样子。

 

其次,关于文件的编码方式。

对于具体的编码方式,被困惑了很久,后来发现其实是很多的概念是混淆的。

为了实现编码的同一,有两个相互独立的机构共同的进行工作,他们分别是,国际标准化组织(ISO)和统一码联盟。说到这里,突然想起来了秦始皇灭六国,统一度量和文字,这难道不是一个道理?!

实际上,Unicode编码系统分为编码方式实现方式两个层次。

所谓编码方式与通用字符集的概念相关,它确定了语言中的每个字符对应的Unicode编码是确定的。目前实际应用的统一码版本对应于UCS-2,使用16位的编码空间。也就是每个字符占用2个字节。这样理论上一共最多可以表示216(即65536)个字符。基本满足各种语言的使用。但是,Unicode对中文的支持不怎么好,资料显示,中、日、韩的三种文字占用了Unicode中0x3000到0x9FFF的部分。因为简体和繁体总共有六七万个汉字,而UCS-2最多能表示65536个,才六万 多个,所以Unicode只能排除一些几乎不用的汉字,好在常用的简体汉字也不过七千多个,为了能表示所有汉字,Unicode也有UCS-4规范,就是用 4个字节来编码字符。

而Unicode的实现方式则是比编码方式复杂的多的问题,这牵扯到具体的操作系统甚至是硬件对于字节的存放方式。简单的说,由于系统和平台的设计不一样,出于硬件,效率,节省空间等等因素的影响,Unicode的具体实现方式有所不同。这里所谓的实现方式从直观上来说,就是写入到文件中的具体数据有所不同。因此,Unicode的实现方式,也称为Unicode的转换格式,即Unicode转换格式(Unicode Transformation Format,简称为UTF)。

而我常常所熟见但却不甚清楚的UTF-8编码就是此类编码方式中的一种。下面简单说明一下。

UTF-8是一种针对Unicode的变长字符编码。他可以表示Unicode标准中的任何字符,并且与ASCII兼容,使得原来针对ASCII的程序或者软件不做或者只做少量的改变即可继续使用。具体来说,UTF-8针对8bit为单位的UCS进行转换,选择边长字节进行存储,其最大长度甚至可以达到六个字节。这样做的最大的好处就是对ASCII的兼容,因为所有ASCII的字符都可以保持原有的字符编码不变。

UCS编码(16进制) UTF-8 字节流(二进制)
0000 - 007F 0xxxxxxx
0080 - 07FF 110xxxxx 10xxxxxx
0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx

举个例子:

平台:Windows7,notepad

输入文本:

1 hello
2 你好

在通常情况下,我们使用ASCII来保存对应的*.txt文档。
这次我分为ASCII,Unicode,UTF-8,Unicode big Endian三种方式来保存,并在winhex下来观察他们保存中英文的情况。

 
首先是 ASCII,可以看出,英文字母在 ASCII下依旧是对应的字符编码,而中文则以两字节为单位进行保存。
字符集——从ASCII到Unicode
 
 
第二格式Unicode,这其实就是Unicode little endian(UTF-16 LE),他与Unicode big endian(UTF-16 BE)是对应的。
字符集——从ASCII到Unicode
 
 
第三种格式Unicode big Endian。
字符集——从ASCII到Unicode
 
以上两种是UTF-16的连个不同版本,到这里可以对以上两种Unicode实现方式进行总结。 Unicode实现方式会在开通写入FF FE两个字节以来区别。并且所有的英文字符(包括windows中回车和换行)都以两个字节为单位进行存储(对英文字符而言这样是大量浪费了空间),而中文字符则是用边长的字符来存储的。上两者唯一的不同之处就在于大小端的问题, 对小端(little endian)而言会把两字节的低位存放在前面,而大端则相反(一个字节内部的顺序不会有变)。
即:字符h的Unicode编码为0x0068,little endian时候存储顺序为 68 00,big endian时候则存储为 00 68。此方法对中文字符作用相同。
 
 
第四种格式 UTF-8,这是今天的主角。
字符集——从ASCII到Unicode
可以看到,UTF-8只是用了16个字节来存储同样的信息。和上两种Unicode相似的是,UTF-8也使用了连个字节的头来标识,分别是 EF BB。在这之后, 对于英文的字符hello以及回车、换行符,UTF-8没有做任何的处理,直接以ASCII的字符形式进行保存(这里顺便提一下,ASCII字符占8位,只表示127个字符,其中最高始终为0)。然而对于中文字符( ASCII字符),UTF-8都使用了三个字节进行保存,这就是体现UTF-8变长的地方。
 
而由Uincode衍生出来的实现方式很多,UTF-8只是其中之一,另外还包括 UTF-32、UTF-16和UTF-7等等。
 
值得注意的是,UTF-16大小端在跨平台的问题上,字符之间的转换会有很大的问题,因为同一数据会被解释成为不同的字符。
例如*上找到的例子,某字符为十六进制编码4E59,按两个字节拆分为4E和59,在Mac上读取时是从低字节开始,那么在Mac OS会认为此4E59编码为594E,找到的字符为“奎”,而在Windows上从高字节开始读取,则编码为U+4E59的字符为“乙”。就是说在 Windows下以UTF-16编码保存一个字符“乙”,在Mac OS环境下打开会显示成“奎”。
这实际上是因为Macintoch一直使用的(UTF-16 BE),而Linux和Windows则使用的是(UTF-16 LE),所以才带来了以上的误解。
 
另外,更有意思的是,*上简体中文关于 UTF-16的说明有说道,Windows的内核和API表现是不一样的,我在windows7下用vs2010检验了一下所给的代码,并没有发现其现象。不知道是真是假。