Java字符编码(⼀)Unicode字符编码
Java 字符编码(⼀)Unicode 字符编码
⼀、字符编码规范
1.1 ASCII(American Standard Code for Information Interchange)
美国信息交换标准代码,这是计算机上最早使⽤的通⽤的编码⽅案。那个时候计算机还只是拉丁⽂字的专利,根本没有想到现在计算机的发展势头,如果想到了,可能⼀开始就会使⽤ unicode 了。当时绝⼤部分专家都认为,要⽤计算机,必须熟练掌握英⽂。这种编码占⽤ 7 个Bit,在计算机中占⽤⼀个字节,8 位,最⾼位没⽤,通讯的时候有时⽤作奇偶校验位。因此 ASCII 编码的取值范围实际上是:0x00-0x7f,只能表⽰ 128 个字符。后来发现 128 个不太够⽤,做了扩展,叫做 ASCII 扩展编码,⽤⾜⼋位,取值范围变成:0x00-0xff,能表⽰ 256 个字符。其实这种扩展意义不⼤,因为 256 个字符表⽰⼀些⾮拉丁⽂字远远不够,但是表⽰拉丁⽂字,⼜⽤不完。所以扩展的意义还是为了下⾯的 ANSI 编码服务。
1.2 ANSI(American National Standard Institite )
美国国家标准协会,也就是说,每个国家(⾮拉丁语系国家)⾃⼰制定⾃⼰的⽂字的编码规则,并得到了 ANSI 认可,符合 ANSI 的标准,全世界在表⽰对应国家⽂字的时候都通⽤这种编码就叫 ANSI 编码。
换句话说,中国的 ANSI 编码和在⽇本的 ANSI 的意思是不⼀样的,因为都代表⾃⼰国家的⽂字编码标准。⽐如中国的 ANSI 对应就是 GB2312 标准,⽇本就是 JIT 标准,⾹港,台湾对应的是 BIG5 标准等等。当然这个问题也⽐较复杂,微软从 95 开始,⽤就是⾃⼰搞的⼀个标准 GBK。GB2312 ⾥⾯只有 6763 个汉字,682 个符号,所以确实有时候不是很够⽤。GBK ⼀直能和 GB2312 相互混淆并且相安⽆事的⼀个重要原因是 GBK 全⾯兼容 GB2312,所以没有出现任何冲突,你⽤GB2312 编码的⽂件通过 GBK 去解释⼀定能获得相同的显⽰效果,换句话说:GBK 对 GB2312 就是,你有的,我也有,你没得的,我还有!
好了,ANSI 的标准是什么呢,⾸先是 ASCII 的代码你不能⽤!也就是说 ASCII 码在任何 ANSI 中应该都是相同的。其他的,你们⾃⼰扩展。所以呢,中国⼈就把 ASCII 码变成 8 位,0x7f 之前我不动你的,我从 0xa0 开始编,0xa0 到 0xff 才 95 个码位,对于中国字那简直是杯⽔车薪,因此,就⽤两个字节吧,此编码范围就从 0xA1A1 - 0xFEFE,这个范围可以表⽰ 23901 个汉字。基本够⽤了吧,GB2312 才 7000多个呢!GBK 更猛,编码范围是从 0x8140 - 0xFEFE,可以表⽰ 3 万多个汉字。可以看出,这两种⽅案,都能保证汉字头⼀个字节在 0x7f 以上,从⽽和 ASCII 不会发⽣冲突。能够实现英⽂和汉字同时显⽰。
BIG5,⾹港和台湾⽤的⽐较多,繁体,范围: 0xA140-0xF9FE,0xA1A1-0xF9FE,每个字由两个字节组成,其第⼀字节编码范围为 0xA1-0xF9,第⼆字节编码范围为 0x40-0x7E 与 0xA1-0xFE,总计收
⼊ 13868 个字 (包括 5401个常⽤字、7652 个次常⽤字、7 个扩充字、以及808 个各式符号)。
那么到底 ANSI 是多少位呢?这个不⼀定!⽐如在 GB2312 和 GBK,BIG5 中,是两位!但是其他标准或者其他语⾔如果不够⽤,就完全可能不⽌两位!
例如:GB18030: GB18030-2000(GBK2K)在 GBK 的基础上进⼀步扩展了汉字,增加了藏、蒙等少数民族的字形。GBK2K 从根本上解决了字位不够,字形不⾜的问题。它有⼏个特点:它并没有确定所有的字形,只是规定了编码范围,留待以后扩充。编码是变长的,其⼆字节部分与 GBK 兼容;四字节部分是扩充的字形、字位,其编码范围是⾸字节 0x81-0xfe、⼆字节 0x30-0x39、三字节 0x81-0xfe、四字节 0x30-0x39。它的推⼴是分阶段的,⾸先要求实现的是能够完全映射到 Unicode3.0 标准的所有字形。它是国家标准,是强制性的。
搞懂了 ANSI 的含义,我们发现 ANSI 有个致命的缺陷,就是每个标准是各⾃为阵的,不保证能兼容。换句话说,要同时显⽰中⽂和⽇本⽂或者阿拉伯⽂,就完全可能会出现⼀个编码两个字符集⾥⾯都有对应,不知道该显⽰哪⼀个的问题,也就是编码重叠的问题。显然这样的⽅案不好,所以 Unicode 才会出现!
1.3 MBCS(Multi-Byte Chactacter System(Set))
多字节字符系统或者字符集,基于 ANSI 编码的原理上,对⼀个字符的表⽰实际上⽆法确定他需要占⽤⼏个字节的,只能从编码本⾝来区分和解释。因此计算机在存储的时候,就是采⽤多字节存储的形式。也就是你需要⼏个字节我给你放⼏个字节,⽐如 A 我给你放⼀个字节,⽐如"中“,我就给你放两个字节,这样的字符表⽰形式就是 MBCS。
在基于 GBK 的 windows 中,不会超过 2 个字节,所以 windows 这种表⽰形式有叫做 DBCS(Double-Byte Chactacter System),其实算是 MBCS 的⼀个特例。C 语⾔默认存放字符串就是⽤的 MBCS 格式。从原理上来说,这样是⾮常经济的⼀种⽅式。
1.4 CodePage
代码页,最早来⾃ IBM,后来被微软,oracle,SAP 等⼴泛采⽤。因为 ANSI 编码每个国家都不统⼀,不兼容,可能导致冲突,所以⼀个系统在处理⽂字的时候,必须要告诉计算机你的 ANSI 是哪个国家和地区的标准,这种国家和标准的代号(其实就是字符编码格式的代号),微软称为 Codepage 代码页,其实这个代码页和字符集编码的意思是⼀样的。告诉你代码页,本质就是告诉了你编码格式。
但是不同⼚家的代码页可能是完全不同,哪怕是同样的编码,⽐如, UTF-8 字符编码在 IBM 对应的代码页是 1208,在微软对应的是65001,在德国的 SAP 公司对应的是 4110 。所以啊,其实本来就是
⼀个东西,⼤家各⾃为政,搞那么多新名词,实在没必要!所以标准还是很重要的
⽐如 GBK 的在微软的代码页是 936,告诉你代码页是 936 其实和告诉你我编码格式是 GBK 效果完全相同。那么处理⽂本的时候就不会有
问题,不会去考虑某个代码是显⽰的韩⽂还是中⽂,同样,⽇⽂和韩⽂的代码页就和中⽂不同,这样就可以避免编码冲突导致计算机不知如何处理的问题。当然⽤这个也可以很容易的切换语⾔版本。但是这都是治标不治本的⽅法,还是⽆法解决同时显⽰多种语⾔的问题,所以最后还是都⽤ unicode 吧,永远不会有冲突了。
1.5 Unicode(Universal Code)
这是⼀个编码⽅案,说⽩了就是⼀张包含全世界所有⽂字的⼀个编码表,只要这个世界上存在的⽂字符号,统统给你⼀个唯⼀的编码,这样就不可能有任何冲突了。不管你要同时显⽰任何⽂字,都没有问题。因此在这样的⽅案下,Unicode 出现了。Unicode 编码范围是:0-
0x10FFFF,可以容纳 1114112 个字符,100 多万啊。全世界的字符根本⽤不完了,Unicode 5.0 版本中,才⽤了 238605 个码位。所以⾜够了。
因此从码位范围看,严格的 unicode 需要 3 个字节来存储。但是考虑到理解性和计算机处理的⽅便性,
理论上还是⽤ 4 个字节来描述。
Unicode 采⽤的汉字相关编码⽤的是《CJK 统⼀汉字编码字符集》— 国家标准 GB13000.1 是完全等同于国际标准《通⽤多⼋位编码字符集(UCS)》 ISO 10646.1。《GB13000.1》中最重要的也经常被采⽤的是其双字节形式的基本多⽂种平⾯。在这 65536 个码位的空间中,定义了⼏乎所有国家或地区的语⾔⽂字和符号。其中从 0x4E00-0x9FA5 的连续区域包含了 20902 个来⾃中国(包括台湾)、⽇本、韩国的汉字,称为 CJK (Chinese Japanese Korean) 汉字。CJK 是《GB2312-80》、《BIG5》等字符集的超集。
⼆、Unicode 中的基本概念
2.1 代码点
Unicode 标准的本意很简单:希望给世界上每⼀种⽂字系统的每⼀个字符,都分配⼀个唯⼀的整数,这些整数叫做代码点(Code Points)。
2.2 代码空间
所有的代码点构成⼀个代码空间(Code Space),根据 Unicode 定义,总共有 1,114,112 个代码点,编号从 0x0-0x10FFFF。换句话说,如果每个代码点都能够代表⼀个有效字符的话,Unicode 标准最
多能够编码 1,114,112,也就是⼤概 110 多万个字符。最新的 Unicode 标准(7.0)已经给超过 11 万个字符分配了代码点。
2.3 代码平⾯
Unicode 标准把代码点分成了 17 个代码平⾯(Code Plane),编号为 #0-#16。每个代码平⾯包含 65,536(2^16)个代码点
(17*65,536=1,114,112)。其中,Plane#0 叫做基本多语⾔平⾯(Basic Multilingual Plane,BMP),其余平⾯叫做补充平⾯(Supplementary Planes)。Unicode7.0 只使⽤了 17 个平⾯中的 6 个,并且给这 6 个平⾯起了名字,如下图所⽰:
下⾯是这些平⾯的名字和⽤途:
1. Plane#0 BMP(Basic Multilingual Plane)⼤部分常⽤的字符都坐落在这个平⾯内,⽐如 ASCII 字符,汉字等。
2. Plane#1 SMP(Supplementary Multilingual Plane)这个平⾯定义了⼀些古⽼的⽂字,不常⽤。
3. Plane#2 SIP(Supplementary Ideographic Plane)这个平⾯主要是⼀些BMP中没有包含汉字。
4. Plane#14 SSP(Supplementary Special-purpose Plane)这个平⾯定义了⼀些⾮图形字符。
5. Plane#15 SPUA-A(Supplementary Private Use Area A)
6. Plane#16 SPUA-B(Supplementary Private Use Area B)
2.4 BMP
BMP 是最重要的⼀个代码平⾯,⼤部分常⽤的字符都定义在这个平⾯内,如下图所⽰:
在 BMP 中定义的代码点包括:
1. ASCII ASCII总共有128个字符,占据了BMP的前128个代码点(上图绿线)
2. ISO-8859-1共256个字符,占据了BMP的前256个代码点(上图绿线+蓝线)
3. CJK Unified Ideographs上图的红⾊区域(占据BMP⼤约1/3)定义了两万多个汉字,其中前 20,902 个汉字是按照《康熙字典》⾥笔画顺
序排列的
4. Surrogate Code Points从 0xD800-0xDBFF 的 1024 个代码点是 High-surrogate 代码点,从 0xDC00-0xDFFF 的 1024 个代码点是 Low-
surrogate 代码点。这 2048 个代码点并不是有效的字符代码点,它们是为 UTF 编码保留的。⼀个 High-surrogate 代码点和⼀个 Low-surrogate 代码点组成⼀个代理对(Surrogate Pair),可以在 UTF-16 ⾥编码 BMP 之外的某个代码点
(1024^2+65,536=1,114,112)。
三、Unicode 编码⽅案
之前提到,Unicode 没有规定字符对应的⼆进制码如何存储。以汉字“汉”为例,它的 Unicode 码点是 0x6c49,对应的⼆进制数是110110*********,⼆进制数有 15 位,这也就说明了它⾄少需要 2 个字节来表⽰。可以想象,在 Unicode 字典中往后的字符可能就需要 3
个字节或者 4 个字节,甚⾄更多字节来表⽰了。
这就导致了⼀些问题,计算机怎么知道你这个 2 个字节表⽰的是⼀个字符,⽽不是分别表⽰两个字符
呢?这⾥我们可能会想到,那就取个最⼤的,假如 Unicode 中最⼤的字符⽤ 4 字节就可以表⽰了,那么我们就将所有的字符都⽤ 4 个字节来表⽰,不够的就往前⾯补 0。这样确实可以解决编码问题,但是却造成了空间的极⼤浪费,如果是⼀个英⽂⽂档,那⽂件⼤⼩就⼤出了 3 倍,这显然是⽆法接受的。
于是,为了较好的解决 Unicode 的编码问题, UTF-8 和 UTF-16 两种当前⽐较流⾏的编码⽅式诞⽣了。当然还有⼀个 UTF-32 的编码⽅式,也就是上述那种定长编码,字符统⼀使⽤ 4 个字节,虽然看似⽅便,但是却不如另外两种编码⽅式使⽤⼴泛。
3.1 UTF-8
UTF-8 是⼀个⾮常惊艳的编码⽅式,漂亮的实现了对 ASCII 码的向后兼容,以保证 Unicode 可以被⼤众接受。
UTF-8 是⽬前互联⽹上使⽤最⼴泛的⼀种 Unicode 编码⽅式,它的最⼤特点就是可变长。它可以使⽤ 1-4 个字节表⽰⼀个字符,根据字符的不同变换长度。编码规则如下:
对于单个字节的字符,第⼀位设为 0,后⾯的 7 位对应这个字符的 Unicode 码点。因此,对于英⽂中的 0 - 127 号字符,与 ASCII 码完全相同。这意味着 ASCII 码那个年代的⽂档⽤ UTF-8 编码打开完全没有问题。
对于需要使⽤ N 个字节来表⽰的字符(N > 1),第⼀个字节的前 N 位都设为 1,第 N + 1 位设为0,剩余的 N - 1 个字节的前两位都设位10,剩下的⼆进制位则使⽤这个字符的 Unicode 码点来填充。
编码规则如下:
Unicode编码(⼗六进制)UTF-8 字节流(⼆进制)
000000-00007F0xxxxxxx
000080-0007FF110xxxxx 10xxxxxx
000800-00FFFF1110xxxx 10xxxxxx 10xxxxxx
010000-10FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
UTF-8 的特点是对不同范围的字符使⽤不同长度的编码。对于 0x00-0x7F 之间的字符,UTF-8 编码与 ASCII 编码完全相同。UTF-8 编码的最⼤长度是 4 个字节。从上表可以看出,4 字节模板有 21 个x,即可以容纳 21 位⼆进制数字。Unicode 的最⼤码位 0x10FFFF 也只有 21位。
例1:“汉”字的 Unicode 编码是 0x6C49。0x6C49 在 0x0800-0xFFFF 之间,使⽤ 3 字节模板:1110x
xxx 10xxxxxx 10xxxxxx。将 0x6C49写成⼆进制是:0110 1100 0100 1001,⽤这个⽐特流依次代替模板中的 x,得到:11100110 10110001 10001001,即 E6 B1 89。
例2:Unicode 编码 0x20C30 在 0x010000-0x10FFFF 之间,使⽤ 4 字节模板:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。将 0x20C30 写成21 位⼆进制数字(不⾜ 21 位就在前⾯补 0):0 0010 0000 1100 0011 0000,⽤这个⽐特流依次代替模板中的 x,得到:11110000 10100000 10110000 10110000,即 F0 A0 B0 B0。
解码的过程也⼗分简单:如果⼀个字节的第⼀位是 0 ,则说明这个字节对应⼀个字符;如果⼀个字节的第⼀位1,那么连续有多少个 1,就表⽰该字符占⽤多少个字节。
3.2 UTF-16
UTF-16 是 Unicode 的⼀种编码⽅式,它⽤两个字节来编码 BMP ⾥的代码点,⽤四个字节编码其余平⾯⾥的代码点(暂不考虑字节顺序)。由于 BMP ⾥只有 65535 个代码点,所以直接把代码点转换成 2 个字节就可以了。BMP 之外的平⾯稍微复杂⼀点,需要先将代码点转化为⼀个代理对,然后再转为 4 个字节。
我们把 Unicode 编码记作 U。编码规则如下:
unicode汉字如果 U<0x10000,U的 UTF-16 编码就是 U 对应的 16 位⽆符号整数(为书写简便,下⽂将 16 位⽆符号整数记作 WORD)。
如果 U≥0x10000,我们先计算 U'=U-0x10000,然后将 U 写成⼆进制形式:yyyy yyyy yyxx xxxx xxxx,U 的 UTF-16 编码(⼆进制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。
为什么 U 可以被写成 20 个⼆进制位?Unicode 的最⼤码位是 0x10FFFF,减去 0x10000 后,U 的最⼤值是 0xFFFFF,所以肯定可以⽤ 20个⼆进制位表⽰。例如:Unicode 编码 0x20C30,减去 0x10000 后,得到 0x10C30,写成⼆进制是:0001 0000 1100 0011 0000。⽤前10 位依次替代模板中的y,⽤后 10 位依次替代模板中的x,就得到:1101100001000011 1101110000110000,即 0xD843 0xDC30。
按照上述规则,Unicode 编码 0x10000-0x10FFFF 的 UTF-16 编码有两个 WORD,第⼀个 WORD 的⾼ 6 位是 110110,第⼆个 WORD 的⾼ 6 位是 110111。可见,第⼀个 WORD 的取值范围(⼆进制)是 11011000 00000000-11011011 11111111,即 0xD800-0xDBFF。第⼆个 WORD 的取值范围(⼆进制)是 11011100 00000000-11011111 11111111,即 0xDC00-0xDFFF。
为了将⼀个 WORD 的 UTF-16 编码与两个 WORD 的 UTF-16 编码区分开来,Unicode 编码的设计者将 0xD800-0xDFFF 保留下来,并称为代理区(Surrogate):
范围说明备注
D800-DB7F High Surrogates⾼位替代
DB80-DBFF High Private Use Surrogates⾼位专⽤替代
DC00-DFFF Low Surrogates低位替代
⾼位替代就是指这个范围的码位是两个 WORD 的 UTF-16 编码的第⼀个 WORD。低位替代就是指这个范围的码位是两个 WORD 的 UTF-16 编码的第⼆个 WORD。
UTF-16 计算规则
假设要编码的补充平⾯内的代码点为 X,具体的编码过程为:
1. X 必定在 0x010000-0x10FFFF 之间
2. 将 X 减去 0x010000,得到的数在 0x0-0xFFFFF 之间,正好可以⽤ 20 个 bit 来表⽰
3. 将⾼位的 10 个 bit 和 0xD800 相加,将地位的 10 个⽐特和 0xDC00 相加,得到的正好是⼀个代理对,也就是四个字节
Unicode3.0 中给出了辅助平⾯字符的转换公式:
High Surrogates:H = Math.floor((c-0x10000) / 0x400)+0xD800
Low Surrogates:L = (c - 0x10000) % 0x400 + 0xDC00
3.3 UTF-32
UTF-32 编码以 32 位⽆符号整数为单位。Unicode 的 UTF-32 编码就是其对应的 32 位⽆符号整数。
3.4 字节序
字节序有两种,分别是“⼤端”(Big Endian, BE)和“⼩端”(Little Endian, LE)。
根据字节序的不同,UTF-16可被实现为UTF-16LE或UTF-16BE,UTF-32可被实现为UTF-32LE或UTF-32BE。例如:
Unicode编码UTF-16LE UTF-16BE UTF32-LE UTF32-BE
0x006C4949 6C6C 4949 6C 00 0000 00 6C 49
0x020C3043 D8 30 DC D8 43 DC 3030 0C 02 0000 02 0C 30
Unicode 标准建议⽤ BOM(Byte Order Mark)来区分字节序,即在传输字节流前,先传输被作为 BOM 的字符“零宽⽆中断空格”。这个字符的编码是 FEFF,⽽反过来的 FFFE(UTF-16)和 FFFE0000(UTF-32)在 Unicode 中都是未定义的码位,不应该出现在实际传输中。
下表是各种 UTF 编码的 BOM:
UTF编码Byte Order Mark (BOM)
UTF-8 without BOM⽆
UTF-8 with BOM EF BB BF
UTF-16LE FF FE
UTF-16BE FE FF
UTF-32LE FF FE 00 00
UTF-32BE00 00 FE FF
参考:
1. 《》:
2. 《Unicode》:
3. 《Unicode的流⾔终结者和编码⼤揭秘》:
4. 《从字节理解Unicode(UTF8/UTF16)》:
5. 《UTF-8、UTF-16、UTF-32 编码》:
6. 《百度百科Unicode》:
7. 《Unicode character table》:
每天⽤⼼记录⼀点点。内容也许不重要,但习惯很重要!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论