Java的char使⽤的编码UTF-16
Java的char使⽤的编码UTF-16
简介
  编码问题⼀直困扰着开发⼈员,尤其在 Java 中更加明显,因为 Java 是跨平台语⾔,不同平台之间编码之间的切换较多。本⽂将向你详细介绍 Java 中编码问题出现的根本原因,你将了解到:Java 中经常遇到的⼏种编码格式的区别;Java 中经常需要编码的场景;出现中⽂问题的原因分析;在开发 Java web 程序时可能会存在编码的⼏个地⽅,⼀个 HTTP 请求怎么控制编码格式?如何避免出现中⽂问题?
为什么要编码
  不知道⼤家有没有想过⼀个问题,那就是为什么要编码?我们能不能不编码?要回答这个问题必须要回到计算机是如何表⽰我们⼈类能够理解的符号的,这些符号也就是我们⼈类使⽤的语⾔。由于⼈类的语⾔有太多,因⽽表⽰这些语⾔的符号太多,⽆法⽤计算机中⼀个基本的存储单元—— byte 来表⽰,因⽽必须要经过拆分或⼀些翻译⼯作,才能让计算机能理解。我们可以把计算机能够理解的语⾔假定为英语,其它语⾔要能够在计算机中使⽤必须经过⼀次翻译,把它翻译成英语。这个翻译的过程就是编码。所以可以想象只要不是说英语的国家要能够使⽤计算机就必须要经过编码。这看起来有些霸道,但是这
就是现状,这也和我们国家现在在⼤⼒推⼴汉语⼀样,希望其它国家都会说汉语,以后其它的语⾔都翻译成汉语,我们可以把计算机中存储信息的最⼩单位改成汉字,这样我们就不存在编码问题了。
所以总的来说,编码的原因可以总结为:
计算机中存储信息的最⼩单元是⼀个字节即 8 个 bit,所以能表⽰的字符范围是 0~255 个
⼈类要表⽰的符号太多,⽆法⽤⼀个字节来完全表⽰
要解决这个⽭盾必须需要⼀个新的数据结构 char,从 char 到 byte 必须编码
如何“翻译”
  明⽩了各种语⾔需要交流,经过翻译是必要的,那⼜如何来翻译呢?计算中提拱了多种翻译⽅式,常见的有 ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16 等。它们都可以被看作为字典,它们规定了转化的规则,按照这个规则就可以让计算机正确的表⽰我们的字符。⽬前的编码格式很多,例如 GB2312、GBK、UTF-8、UTF-16 这⼏种格式都可以表⽰⼀个汉字,那我们到底选择哪种编码格式来存储汉字呢?这就要考虑到其它因素了,是存储空间重要还是编码的效率重要。根据这些因素来正确选择编码格式,下⾯简要介绍⼀下这⼏种编码格式。
ASCII 码
java语言使用的字符码集是 学过计算机的⼈都知道 ASCII 码,总共有 128 个,⽤⼀个字节的低 7 位表⽰,0~31 是控制字符如换⾏回车删除等;32~126 是打印字符,可以通过键盘输⼊并且能够显⽰出来。 
ISO-8859-1(扩展ASCII编码)
128 个字符显然是不够⽤的,于是 ISO 组织在 ASCII 码基础上⼜制定了⼀些列标准⽤来扩展 ASCII 编码,它们是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵盖了⼤多数西欧语⾔字符,所有应⽤的最⼴泛。ISO-8859-1 仍然是单字节编码,它总共能表⽰ 256 个字符。 
GB2312
它的全称是《信息交换⽤汉字编码字符集 基本集》,它是双字节编码,总的编码范围是 A1-F7,其中从 A1-A9 是符号区,总共包含 682个符号,从 B0-F7 是汉字区,包含 6763 个汉字。 
GBK(扩展GB2312)
全称叫《汉字内码扩展规范》,是国家技术监督局为 windows95 所制定的新的汉字内码规范,它的出现是为了扩展 GB2312,加⼊更多的汉字,它的编码范围是 8140~FEFE(去掉 XX7F)总共有 23940 个码位,它能表⽰ 21003 个汉字,它的编码是和 GB2312 兼容的,也就是说⽤ GB2312 编码的汉字可以⽤ GBK 来解码,并且不会有乱码。 
GB18030(兼容GB2312)
全称是《信息交换⽤汉字编码字符集》,是我国的强制标准,它可能是单字节、双字节或者四字节编码,它的编码与 GB2312 编码兼容,这个虽然是国家标准,但是实际应⽤系统中使⽤的并不⼴泛。 
Unicode编码集
ISO 试图想创建⼀个全新的超语⾔字典,世界上所有的语⾔都可以通过这本字典来相互翻译。可想⽽知这个字典是多么的复杂,关于Unicode 的详细规范可以参考相应⽂档。Unicode 是 Java 和 XML 的基础,下⾯详细介绍 Unicode 在计算机中的存储形式。
UTF-16
UTF-16 具体定义了 Unicode 字符在计算机中存取⽅法。UTF-16 ⽤两个字节来表⽰ Unicode 转化格式,这个是定长的表⽰⽅法,不论什么字符都可以⽤两个字节表⽰,两个字节是 16 个 bit,所以叫 UTF-16。UTF-16 表⽰字符⾮常⽅便,每两个字节表⽰⼀个字符,这个在字符串操作时就⼤⼤简化了操作,这也是 Java 以 UTF-16 作为内存的字符存储格式的⼀个很重要的原因。
UTF-8
UTF-16 统⼀采⽤两个字节表⽰⼀个字符,虽然在表⽰上⾮常简单⽅便,但是也有其缺点,有很⼤⼀部分字符⽤⼀个字节就可以表⽰的现在要两个字节表⽰,存储空间放⼤了⼀倍,在现在的⽹络带宽还⾮常有限的今天,这样会增⼤⽹络传输的流量,⽽且也没必要。⽽ UTF-8 采⽤了⼀种变长技术,每个编码区域有不同的字码长度。不同类型的字符可以是由 1~6 个字节组成。
UTF-8 有以下编码规则:
如果⼀个字节,最⾼位(第 8 位)为 0,表⽰这是⼀个 ASCII 字符(00 - 7F)。可见,所有 ASCII 编码已经是 UTF-8 了。
如果⼀个字节,以 11 开头,连续的 1 的个数暗⽰这个字符的字节数,例如:110xxxxx 代表它是双字节 UTF-8 字符的⾸字节。
如果⼀个字节,以 10 开始,表⽰它不是⾸字节,需要向前查才能得到当前字符的⾸字节
char c = '我';
但是事实并不是那么简单,Java的char内部编码为UTF-16,请参考String编码(⼆) 证明JAVA的char编码为UTF-16
Java 的char⽤两字节存储,表⽰范围从 '\u0000' 到 '\uffff' ,也就是从0到65535。事实上,⼀个 char不能表⽰65535个字符,因为只有U+0000 到 U+D7FF 和 U+E000 到U+FFFF能⽤来表⽰ ⼀个完整的字符,这些叫做 BMP(Basic Multilingual Plane),另外的作为high surrogate和 low surrogate 拼接组成由4字节表 ⽰的字符。
在UTF-16编码中,⼤于U+10000码位将被编码为⼀对16⽐特长的码元,即按4个字节编码,此时char⽆法表⽰。utf16编码格式
所以Java的char只能表⽰utf16中的bmp部分字符。对于CJK(中⽇韩统⼀表意⽂字)部分扩展字符集则⽆法表⽰。
例如,下图中除Ext-A部分,char均⽆法表⽰。
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个平⾯起了名字,如下图所⽰:
下⾯是这些平⾯的名字和⽤途:
Plane#0 BMP(Basic Multilingual Plane)⼤部分常⽤的字符都坐落在这个平⾯内,⽐如ASCII字符,汉字等。
Plane#1 SMP(Supplementary Multilingual Plane)这个平⾯定义了⼀些古⽼的⽂字,不常⽤。
Plane#2 SIP(Supplementary Ideographic Plane)这个平⾯主要是⼀些BMP中没有包含汉字。
Plane#14 SSP(Supplementary Special-purpose Plane)这个平⾯定义了⼀些⾮图形字符。
Plane#15 SPUA-A(Supplementary Private Use Area A)
Plane#16 SPUA-B(Supplementary Private Use Area B)
UTF-16是Unicode字符集的⼀种转换⽅式,即把Unicode的码位转换为16⽐特长的码元串⾏,以⽤于数据存储或传递。UTF-16编码规则如下:
2.2.1 从U+D800到U+DFFF的码位(代理区)
因为Unicode字符集的编码值范围为0-0x10FFFF,⽽⼤于等于0x10000的辅助平⾯区的编码值⽆法⽤2个字节来表⽰,所以Unicode标准规定:基本多语⾔平⾯内,U+D800..U+DFFF的值不对应于任何字
符,为代理区。因此,UTF-16利⽤保留下来的0xD800-0xDFFF区段的码位来对辅助平⾯的字符的码位进⾏编码。
但是在使⽤UCS-2的时代,U+D800..U+DFFF内的值被占⽤,⽤于某些字符的映射。但只要不构成代理对,许多UTF-16编码解码还是能把这些不符合Unicode标准的字符映射正确的辨识、转换成合规的码元. 按照Unicode标准,这种码元串⾏本来应算作编码错误.
2.2.2 从U+0000⾄U+D7FF以及从U+E000⾄U+FFFF的码位
第⼀个Unicode平⾯(BMP),码位从U+0000⾄U+FFFF(除去代理区),包含了最常⽤的字符。UTF-16与UCS-2编码在这个范围内的码位为单个16⽐特长的码元,数值等价于对应的码位。BMP中的这些码位是仅有的码位可以在UCS-2被表⽰。
2.2.3 从U+10000到U+10FFFF的码位
辅助平⾯(Supplementary Planes)中的码位,⼤于等于0x10000,在UTF-16中被编码为⼀对16⽐特长的码元(即32bit,4Bytes),称作 code units called a 代理对(surrogate pair),具体⽅法是:
Ø 码位减去0x10000, 得到的值的范围为20⽐特长的0..0xFFFFF(因为Unicode的最⼤码位是0x10ffff,减去0x10000后,得到的最⼤值是0xfffff,所以肯定可以⽤20个⼆进制位表⽰),写成⼆进制
形式:yyyy yyyy yyxx xxxx xxxx。
Ø ⾼位的10⽐特的值(值的范围为0..0x3FF)被加上0xD800得到第⼀个码元或称作⾼位代理(high surrogate), 值的范围是
0xD800..0xDBFF。由于⾼位代理⽐低位代理的值要⼩,所以为了避免混淆使⽤,Unicode标准现在称⾼位代理为前导代理(lead surrogates)。
Ø 低位的10⽐特的值(值的范围也是0..0x3FF)被加上0xDC00得到第⼆个码元或称作低位代理(low surrogate), 现在值的范围是
0xDC00..0xDFFF。 由于低位代理⽐⾼位代理的值要⼤,所以为了避免混淆使⽤,Unicode标准现在称低位代理为后尾代理(trail surrogates)。
Ø 最终的UTF-16(4字节)的编码(⼆进制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。
按照上述规则,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。上⾯所说的从U+D800到U+DFFF的码位(代理区),就是为了将⼀个WORD(2字节)的UTF-16编码与两个WORD的UTF-16编码区分开来。
由于⾼位代理、低位代理、BMP中的有效字符的码位,三者互不重叠,搜索是简单的: ⼀个字符编码的⼀部分不可能与另⼀个字符编码的不同部分相重叠。这意味着UTF-16是⾃同步(self-synchronizing):可以通过仅检查⼀个码元就可以判定给定字符的下⼀个字符的起始码元。 UTF-8也有类似优点,但许多早期的编码模式就不是这样,必须从头开始分析⽂本才能确定不同字符的码元的边界。
由于最常有的字符都在基本多⽂种平⾯中,许多软件的处理代理对的部分往往得不到充分的测试。这导致了⼀些长期的bug与潜在安全漏洞,甚⾄在⼴为流⾏得到良好评价的应⽤软件

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。