字符编码杂谈

一、为什么要编码,编码发展历史?
经常给人带来疑惑是选择文件的编码方式,Windows上默认是ANSI,为什么文件常常出现乱码呢?Unicode、UTF-8、GBx都是嘛意思?本文为你一一道来。

 

如何在计算机上实现一个字符显示与存储功能呢?——字库。
为什么要编码?为了让计算机能够识别每个字符,就必须给它分配一个计 算机能识别的名字,这个名字就是字符的编码,计算机在可以存储该编码值以保存信息,显示该字符时,根据该编码值去字库中查找对应的点阵字符图(或者绘制该 字符的方法),然后在显示器上绘制出来,显示给用户。

最早美国人发明计算机,英文字符+数字+标点符号+部分特殊字符数量也不多,当时设计了ASCII用一个字节0~127就编码完全了,编程语言的char型的长度就是一个字节。但是后来计算机走向世界,带来了新的问题,其他语言的字符在一个字节的容量里剩下的128空间也包容不下啊, 这个时候字符编码开始混乱了,经历了好久才又重新得到了统一 ——Unicode编码。

编码发展简史:点阵字库(编码就是索引号) --> ASCII --> MBCS (ANSI) --> Unicode

二、术语定义:

ASCII (American Standard Code for Information Interchange)  美国信息交换标准码, 在文字资料转送中用来数字显现字码的标准七点代码 (计算机用语)  字符容量2^7=128
eg:ISO-8859-1是兼容ASCII编码的单字节编码,增加了128~255之间的控制字符。

MBCS 多字节码,一般使用双字节,为了区别于ASCII字符,其每个字节均大于0x7F。由各个国家自己定义,相互之间不兼容,必须配合代码页才能被正确的解析。字符容量2^14=16384
eg: GB2312、BIG5、GBK、GB18130、EUC-KR(韩文)、SHIFT-JIS(日文)等等都属于MBCS编码。

Unicode:(统一码、万国码、单一码)是一种在计算机上使用的字符编码。它为每种语言 中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。Unicode字符集可以简写为UCS(Unicode Character Set)。早期的Unicode标准有UCS-2、UCS-4的说法。 UCS4四字节正规形式用4个字节编码,UCS2双字节基本平面形式用2个字节编码。Unicode通过 Unicode的语言区域码实现了将每一种MBCS都与Unicode建立一种映射关系。字符容量2^32=4294967296
eg:UTF-8、UTF-16、UCS2等等都属于Unicode编码。

故而:Unicode>MBCS>ASCII,从小到大的转换是可逆的,而从大到小的转换有可能丢失信息,所以不可逆。

具体的编码术语定义:
GBx:GB2312是常用简体字符集,GBK加入了繁体字、日文假名、希腊字符等、GB18130又加入了藏文。说白了GB18130是最大的超集。(GBx的文字编码是用双字节来表示的,即不论中、英文字符均使用双字节来表示,为了区分中文,将其最高位都设定成1。GB18130兼容GBK,GBK兼容GB2312。

UTF-8:Unicode Transformation Format-8bit,允许含BOM,但通常不含BOM。是用以解决国际上字符的一种多字节编码,它对英文使用8位(即一个字节),中文使用24为(三个字节)来编码。UTF-8包含全世界所有国家需要用到的字符,是国际编码,通用性强。

GBx等与UTF8之间都必须通过UCS编码才能相互转换。
(再强调一下:注意UTF8到GBx不完全可逆,可能丢失信息!)

三、更深入的解释:

UCS-2、UCS-4、BMP:早期的Unicode标准有UCS-2、UCS-4的说法。UCS-2用两个字节编码,UCS-4用4个字节编码。UCS-4根据最高位为0的最高字节分 成2^7=128个group。每个group再根据次高字节分为256个平面(plane)。每个平面根据第3个字节分为256行 (row),每行有256个码位(cell)。group 0的plane 0被称作BMP(Basic Multilingual Plane)。 由于目前的UCS-4规范中还没有任何字符被分配在BMP之外,所以将UCS-4的BMP去掉(即前面的两个全零字节)就得到了UCS-2。 

UTF-x编码:
UTF-8
就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下:

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

例如 “汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx10xxxxxx。将6C49写成二进制是:0110 110001 001001, 用这个比特流依次代替模板中的x,得到:1110011010110001 10001001,即E6 B1 89。

UTF-16以16位为单元对UCS进行编码。对于小于0x10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于 0x10000的UCS码,定义了一个算法。不过由于实际使用的UCS2,或者UCS4的BMP必然小于0x10000,所以就目前而言,可以认为 UTF-16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。

字节序big endian /little endian:
UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。 例如“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎”还是 “乙”?
Unicode规范中推荐的标记字节顺序的方法是BOM。

BOM:BOM是Byte order Mark。在UCS编码中有一个叫做"ZERO WIDTH NO-BREAKSPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输 字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。

这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字 符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。

UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAKSPACE"的UTF-8编码是EF BB BF ,如果接收者收到以EF BB BF开头的字节流,就一改以UTF-8编码进行解释和处理。(EF BB BF实际上对应UCS2中的FFFF,确保不会和有效UTF-8字符冲突)

四、扩展1——更多术语

GB2312的本质上还是区位码,为了兼容00-7f的ASCII编码,从区位码到内码,需要在高字节和低字节上分别加上0xA0。(即10100000)

区位码:国家1980年的一个标准《中华人民共和国国家标准 信息交换用汉字编码字符集 基本集 GB2312-80》。这个标准用两个数来编码汉字和中文符号。第一个数称为“区”,第二个数称为“位”。所以也称为区位码。1-9区是中文符号,16-55区是一级汉字,56-87区是二级汉字。

eg:“啊"的区位码是1601,写成16进制是0x10,0x01。这和计算机广泛使用的ASCII编码冲突。为了兼容00-7f的ASCII编码,我们在区位码 的高、低字节上分别加上A0。这样“啊”的编码就成为B0A1。我们将加过两个A0的编码也称为GB2312编码。

内码:内码是指操作系统内部的字符编码,早期操作系统的内码是与语言相关的。现在的Windows在系统内部支持Unicode,然后用代码页适应各种语言,“内码”的概念就比较模糊了。微软一 般将缺省代码页指定的编码说成是内码。

代码页:所谓代码页(code page)就是针对一种语言文字的字符编码。例如GBK的code page是CP936,BIG5的code page是CP950,GB2312的code page是CP20936。
Windows按照当前的缺省代码页去解释文本文件里的字节流。缺省代码页可以通过控制面板的区域选项设置。记事本的另存为中有一项ANSI,其实就是按照缺省代码页的编码方法保存。
Windows的内码是Unicode,它在技术上可以同时支持多个代码页。只要文件能说明自己使用什么编码,用户又安装了对应的代码页,Windows 就能正确显示,例如在HTML文件中就可以指定charset。

 

五、扩展2——编程中需要注意的编码问题

要避免不正确的使用Unicode:

1. Unicode -->MBCS      例如UTF8_to_GB2312等

2. 对变长编码进行substring()   例如UTF8是一种变长编码!

3. 存储字符的时候未保存编码   以后解析会发生困难

4. 使用非Unicode字符作为接口  导致程序不能支持多语种

5. 注意:如果在程序的任何一处使用了非Unicode编码,则该程序为非Unicode程序! 


六、扩展3——For linuxer:
Latin1:是ISO-8859-1 的别名,有些环境下写作Latin-1。ISO-8859-1编码是单字节编码,向下兼容ASCII,其编码范围是0x00-0xFF,0x00-0x7F之间完全和ASCII一 致,0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号。 (ASCII编码是一个7位的容器,ISO-8859-1编码是一个8位的容器。)

enc-cn: == cp936  == GBK

大部分Linux发行版记录locale(系统默认使用语言)的文件是/etc/sysconfig/i18n,如果默认安装的是中文的系统,i18n的内容如下:
LANG="zh_CN.UTF-8"
SYSFONT="latarcyrheb-sun16"
SUPPORTED="zh_CN.UTF-8:zh_CN:zh"

vi中文乱码问题:
vi中有3个变量和编码相关:
encoding—-该选项使用于缓冲的文本(你正在编辑的文件),寄存器,Vim 脚本文件等等。你可以把 ‘encoding’ 选项当作是对 Vim 内部运行机制的设定。默认值:与系统当前locale相同
fileencoding—-该选项是vim写入文件时采用的编码类型。默认值:自动辨认文件编码,但是会误判GBx为Latin1,对于新文件默认使用encoding值
termencoding—-该选项代表输出到客户终端(Term)采用的编码类型。默认值:为空,也就是输出到终端不进行编码转换

中文乱码问题就在于这三个编码不一致。

例如:encoding=utf-8(locale决定的),fileencoding=latin1(自动编码判断机制不准导致 的,实际为GB2312),termencoding=空(默认无需转换term编码)。实际终端使用cp936,显示中文为乱码。

解决方法:
方案一:
step1:重新以正确的编码方式(cp936)加载文件为:edit ++enc=cp936 ,可以简写为:e ++enc=cp936。(不可使用set fileencoding=cp936,这只是将文件保存为cp936)
step2:set termencoding=cp936将终端数据输出为终端所使用的编码(这里终端使用cp936编码)
或者是 set encoding=cp936 (即设置vi的内部编码和终端编码一致即可)
方案二:临时改变vi运行的locale环境,方法是以LANG=zh_CN vi filename的方式来启动vi,则此时encoding=euc-cn,和终端一致,不会乱码。

Linux local的设置

设置/etc/sysconfig/i18n,或者~/i18n,
LANG="zh_CN.UTF-8"
SYSFONT="latarcyrheb-sun16"
SUPPORTED="zh_CN.UTF-8:zh_CN:zh"

或者 vi ~/.bash_profile:
export LANG="zh_CN.UTF-8"
export LANGUAGE="zh_CN.UTF-8"
export LC_CTYPE="zh_CN.UTF-8"

或者直接export LC_ALL="zh_CN.UTF-8"

更多参考:关于linux的local

vi/vim的默认encoding设置
$cp /etc/vim/vim ~/.vimrc    or    $cp /etc/virc ~/.vimrc
编辑 ~/.vimrc 文件,加入:
set encodings=utf-8
set fileencodings=utf-8,gb18030,latin1
或者根据实际情况加入编码设置选项的值。

更多参考 VIM中文设置,支持CJK多种文件编码和locale设定

posted @ 2010-08-24 18:12  thunder123  阅读(802)  评论(2编辑  收藏  举报