大话字符编码

     字符编码这个东西实在是头疼,一会儿是ISO5589-1,一会儿是GB2312,一会儿又是UTF-860%以上的程序员对这个东西稀里糊涂,前些天查了一些文档,好好梳理了一下,把自己的心得分享给大家。

    首先,我们来明确一些概念:

字符:它是抽象的最小文本单位。字符就是对某种意义的图画表示,或者说形状表示。“A”是一个字符,“¥”也是一个字符。

 

字符集:字符的集合。比如汉字字符集,拉丁字符集,全人类所有的字符的集合。

 

编码字符集:也就是一个字符集的编码形式,它为每一个字符分配一个唯一数字。

在没有包含全人类的字符编码集出来之前,各个国家都是你编你的,我编我的,也就出现了西欧语言(又叫Latin-1)编码字符集(ISO 8859-1),中文编码字符集(GB2312/BIG5/GBK/GB18030),日文编码字符集,CJK中日韩统一编码字符集等等。

这样可不行,得有对全人类字符的统一考虑才行,也就是我们通常说的国际化考虑,英文是Internationalization,这个单词可够长的,于是就简写做i18n,表示i开头n结尾的18个字母的单词。

为了i18n,于是出现了两个针对全人类所有字符的编码字符集,一个是UCSUniversal Character Set,它是国际标准化组织弄出来的,也就是ISO/IEC 10646

另一个是UnicodeUniversal CodeUnicode同时也是一个联盟的名字,也就是HPMicrosoftIBMApple等几家知名的大型计算企业所组成的联盟集团,他们为了推进多种文种的统一编码而制定出了Unicode(通常我们说Unicode指的是UTF-16)。

这两个组织同时干了同一件事,就是给全人类的字符进行编码,这可不是一件好事,想当年巴比伦塔就是因为上帝搞乱了俺们人类的语言才没有建成的,所以在91年的时候,国际标准化组织做了让步,将UnicodeUCS进行统一,(也就是把Unicode并为ISO10646的第一个字面BMP,这个我们后面再讲)。

所以,实际上,现在的情况是:全人类现有所有字符是Unicode的一个子集,而Unicode又是UCS的一个子集。

 

代码点:是指可用于编码字符集的数字。编码字符集定义一个有效的代码点范围,但是并不一定将字符分配给所有这些代码点(哪有那么多字符啊?呵呵)。

有效的UCS的代码点范围是0~231次方。

有效的 Unicode 代码点范围是 U+0000 U+10FFFFUnicode 标准始终使用十六进制数字,而且在书写时在前面加上前缀“U+”,比如“A”的编码书写为“U+0041”

 

字符编码方案:是从一个或多个编码字符集到一个或多个固定宽度代码单元序列的映射。最常用的代码单元是字节,但是 16 位或 32 位整数也可用于内部处理。

ISO 10646就是UCS的字符编码方案。

UTF-32UTF-16 UTF-8 Unicode 标准的编码字符集的字符编码方案。

前面说的ISO 8859-1GB2312/BIG5/GBK/GB18030CJK等,都是某种语言的字符编码方案。而不考虑语言的统一方案必定是ISO10646或者UTF-32UTF-16 UTF-8中的一种。

 

好了,有了这些概念,下面我可以来具体的讲讲ISO 10646Unicode的编码方案了。

ISO/IEC 10646:它采用4个字节来表示一个字符,实际使用的是31bit,采用十六进制全编码表示。

总体结构是一个四维的编码空间,先将所有的代码点分为128个三维组(group),每一组包含256个平面(plane),每一个平面包含256(row),每一行包含256个字位(cell),又称谓“列”。其中group的值范围是从007Fplanerowcell的值范围都是从00FF全编码。整个编码字符集的每个字符都是由4个八位序列表示,按照组八位、面八位、行八位、列八位的顺序,具体表示如下:

 

Group-octet

  组八位

 Plane-octet

 平面八位

 Row-octet

  行八位

 Cell-octet

 字位八位

 

ISO/IEC 10646将其第一个平面(00组中的00平面)称作Basic Multilingual Plane(基本多文种平面),简称BMP。人类对BMP中的256×256=65536个字符的使用频率超过99.9%,这就使得人们对BMP格外青睐。ISO/IEC 10646规定BMP上的字符可以作为双八位编码字符集使用,即:在此平面上仅用行、列两个八位就可以表示一个编码字符。

按照行可以将BMP中的代码点分为下面四个区域:

A- Zone004D行)为拼音文字编码区,拉丁文、阿拉伯文、日文的平假名及片假名、数学符号等等都在此区域编码。一句话,除了汉字以外,目前世界上已规范的文种都在此区域编码。

I- Zone4E9F行)为表意文字编码区,我们将其称作汉字区,通常人们所说的CJK统一编码汉字就放在这个区域,从4E009FA520902个编码汉字。

O- ZoneA0DF行)是一个开放区域,未作定义,留作扩展用。(其中的D8DF行为UTF-16的代理区,后面再讲。)

R- ZoneE0FF行)为限制使用区,一些兼容字符、字符的变形显现形式、特殊字符等均放在此区。

韩文在1993年的ISO/IEC 10646-1中属于A –Zone344D行,但后来由于扩充的需要被迁徙到O- ZoneACD744行(44X256=11264);而原来的344D行被如下分配:

CJK扩充集A       34004DB5

⒉康熙字典214部首   2F002FD5

CJK部首扩充       2E802EF3

⒋汉字结构符         2FF02FF8

⒌藏文               0F000FCF

⒍彝文               A000A4C6

⒎蒙文               180018A9

 

UTF-16:也就是我们通常所说的Unicode编码,它用2个字节或者4个字节来表示一个字符。

前面说了,国际标准化组织和Unicode的合并是把Unicode并为ISO10646的第一个字面BMP,也就是说Unicode中代码点在U+0000U+FFFF间的字符和BMP中的完全一样。也就是说,BMP中的字符,在UFT-16中是用2个字节来表示的。

Unicode的代码点范围是U+0000 U+10FFFF ,所以我们把Unicode代码点在 U+10000 U+10FFFF 范围之间的字符称为增补字符。但增补字符在Unicode编码(UFT-16)中怎么表示呢?

前面讲到,在BMP中定义了一个代理区(Surrogate Zone(D800DFFF), 这个区域内没有定义任何的字符或符号。将这个区域平分为前后两个各容纳10241K)个编码的区域(D800-DBFFDC00-DFFF),分别称作高半代理(high surrogate)及低半代理(low surrogate)区域。从这两个区域分别各取一个编码,由这两个编码组合成一个4 bytes代理对(surrogate pair)来表示一个编码字符,而且只有将这两个代理对(surrogate pair)结合在一起才能表示一个字符,单独使用其中的任何一个都没有意义。

高低半字符的编码位置各为1,0244×256,因此UTF-16总计可提供(4×256×4×256)=16×65536个编码位置,亦即16个字面,也就是U+0000 U+10FFFF。对BMP而言,当然无需使用UTF-16转码,所以UTF-16的转码主要应用于ISO10646的第1~第14字面(第15字面为专用字面),也就是说只有第1~第14字面的字符才需要两个UTF-16编码来表示,即4个字节表示一个增补字符。

从上面的分析我们看到,用2个字节的Unicode就可以表示使用频率超过99.9%BMP中的字符了。但现实的情况是,现在的计算机和网络中主要使用的字符绝大多数属于ASCII字符集,也就是基本拉丁字符集(U+0000 U+007F)的127个字符。针对这种情况,Unicode组织特别设计了UTF-8编码。

 

UTF-8:使用14个字节的序列对 Unicode 代码点进行编码。

U+0000 U+007F 使用1个字节编码,U+0080 U+07FF 使用2个字节,U+0800 U+FFFF 使用3个字节,而 U+10000 U+10FFFF(增补字符) 使用4个字节。编码规则如下:

U+0000  – U+007F:

0xxxxxxx

U+0080  – U+07FF:

110xxxxx 10xxxxxx

U+0800  – U+FFFF:

1110xxxx 10xxxxxx 10xxxxxx

U+10000 – U+10FFFF:

11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

 

Java modified UTF-8

Java内部对UTF-8进行了一定修改,这样,它和标准UTF-8编码就在一定程度上不能兼容了,原因有两点:

1、经修订的 UTF-8 将字符 U+0000 表示为双字节序列 0xC0 0x80,而标准 UTF-8 使用单字节值 0x0

2、经修订的 UTF-8 通过对其 UTF-16 表示法的两个代理代码单元单独进行编码来表示增补字符。每个代理代码单元由三个字节来表示,这样,经修改的UTF-8就需要6个字节来表示一个增补字符,而标准 UTF-8 使用4个字节序列表示一个增补字符。

Java java.io.DataInput DataOutput 接口和类中使用经修订的 UTF-8 实现。而标准 UTF-8 String 类、java.io.InputStreamReader OutputStreamWriter 类、java.nio.charset以及许多其上层的 API 提供支持。

这可真是个大陷阱哪,虽然用到增补字符的机会不多,但U+0000却是经常使用的,用Java做开发的同志们一定要注意了!

Tip:在Linux中,可以用od –x FileName或者hexdump FileName这样的命令来输出文件的16进制形式,这样就可以看到U+0000之类的不可打印字符了,:)

 

UTF-32:即将每一个 Unicode 代码点表示为相同值的 32 位整数。很明显,它是内部处理最方便的表达方式,但是,如果作为一般字符串表达方式,则要消耗更多的内存。所以实际上它只有存在的意义,而没有实用的意义,没有哪个傻瓜会真的去做UTF-32编码。

 

通过这些介绍,相信大家对编码的相关概念和i18n问题、ISO 10646BMPUnicode及其3种编码方式有了一个大概的了解,至少以后再看到一些奇怪的编码代号应该不会茫茫然而不知所措了。

另外,在Linux /usr/share/i18n/charmaps/ 目录下有一些压缩包,存的就是各种编码方案对应的编码和Unicode代码点的对应,需要的时候可以参考。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值