Board logo

标题: 深入分析 Java 中的中文编码问题 (4) [打印本页]

作者: look_w    时间: 2018-9-23 12:44     标题: 深入分析 Java 中的中文编码问题 (4)

按照 ISO-8859-1 编码字符串“I am 君山”用 ISO-8859-1 编码,下面是编码结果:
从上图看出 7 个 char 字符经过 ISO-8859-1 编码转变成 7 个 byte 数组,ISO-8859-1 是单字节编码,中文“君山”被转化成值是 3f 的 byte。3f 也就是“?”字符,所以经常会出现中文变成“?”很可能就是错误的使用了 ISO-8859-1 这个编码导致的。中文字符经过 ISO-8859-1 编码会丢失信息,通常我们称之为“黑洞”,它会把不认识的字符吸收掉。由于现在大部分基础的 Java 框架或系统默认的字符集编码都是 ISO-8859-1,所以很容易出现乱码问题,后面将会分析不同的乱码形式是怎么出现的。
按照 GB2312 编码字符串“I am 君山”用 GB2312 编码,下面是编码结果:
GB2312 对应的 Charset 是 sun.nio.cs.ext. EUC_CN 而对应的 CharsetDecoder 编码类是 sun.nio.cs.ext. DoubleByte,GB2312 字符集有一个 char 到 byte 的码表,不同的字符编码就是查这个码表找到与每个字符的对应的字节,然后拼装成 byte 数组。查表的规则如下:
1
c2b[c2bIndex[char >> 8] + (char & 0xff)]




如果查到的码位值大于 oxff 则是双字节,否则是单字节。双字节高 8 位作为第一个字节,低 8 位作为第二个字节,如下代码所示:
1
2
3
4
5
6
7
8
9
10
if (bb > 0xff) {    // DoubleByte
           if (dl - dp < 2)
               return CoderResult.OVERFLOW;
           da[dp++] = (byte) (bb >> 8);
           da[dp++] = (byte) bb;
} else {                      // SingleByte
           if (dl - dp < 1)
               return CoderResult.OVERFLOW;
           da[dp++] = (byte) bb;
}




从上图可以看出前 5 个字符经过编码后仍然是 5 个字节,而汉字被编码成双字节,在第一节中介绍到 GB2312 只支持 6763 个汉字,所以并不是所有汉字都能够用 GB2312 编码。
按照 GBK 编码字符串“I am 君山”用 GBK 编码,下面是编码结果:
你可能已经发现上图与 GB2312 编码的结果是一样的,没错 GBK 与 GB2312 编码结果是一样的,由此可以得出 GBK 编码是兼容 GB2312 编码的,它们的编码算法也是一样的。不同的是它们的码表长度不一样,GBK 包含的汉字字符更多。所以只要是经过 GB2312 编码的汉字都可以用 GBK 进行解码,反过来则不然。
按照 UTF-16 编码字符串“I am 君山”用 UTF-16 编码,下面是编码结果:
用 UTF-16 编码将 char 数组放大了一倍,单字节范围内的字符,在高位补 0 变成两个字节,中文字符也变成两个字节。从 UTF-16 编码规则来看,仅仅将字符的高位和地位进行拆分变成两个字节。特点是编码效率非常高,规则很简单,由于不同处理器对 2 字节处理方式不同,Big-endian(高位字节在前,低位字节在后)或 Little-endian(低位字节在前,高位字节在后)编码,所以在对一串字符串进行编码是需要指明到底是 Big-endian 还是 Little-endian,所以前面有两个字节用来保存 BYTE_ORDER_MARK 值,UTF-16 是用定长 16 位(2 字节)来表示的 UCS-2 或 Unicode 转换格式,通过代理对来访问 BMP 之外的字符编码。
按照 UTF-8 编码字符串“I am 君山”用 UTF-8 编码,下面是编码结果:
UTF-16 虽然编码效率很高,但是对单字节范围内字符也放大了一倍,这无形也浪费了存储空间,另外 UTF-16 采用顺序编码,不能对单个字符的编码值进行校验,如果中间的一个字符码值损坏,后面的所有码值都将受影响。而 UTF-8 这些问题都不存在,UTF-8 对单字节范围内字符仍然用一个字节表示,对汉字采用三个字节表示。它的编码规则如下:
清单 3.UTF-8 编码代码片段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
private CoderResult encodeArrayLoop(CharBuffer src,
ByteBuffer dst){
           char[] sa = src.array();
           int sp = src.arrayOffset() + src.position();
           int sl = src.arrayOffset() + src.limit();
           byte[] da = dst.array();
           int dp = dst.arrayOffset() + dst.position();
           int dl = dst.arrayOffset() + dst.limit();
           int dlASCII = dp + Math.min(sl - sp, dl - dp);
           // ASCII only loop
           while (dp < dlASCII && sa[sp] < '\u0080')
               da[dp++] = (byte) sa[sp++];
           while (sp < sl) {
               char c = sa[sp];
               if (c < 0x80) {
                   // Have at most seven bits
                   if (dp >= dl)
                       return overflow(src, sp, dst, dp);
                   da[dp++] = (byte)c;
               } else if (c < 0x800) {
                   // 2 bytes, 11 bits
                   if (dl - dp < 2)
                       return overflow(src, sp, dst, dp);
                   da[dp++] = (byte)(0xc0 | (c >> 6));
                   da[dp++] = (byte)(0x80 | (c & 0x3f));
               } else if (Character.isSurrogate(c)) {
                   // Have a surrogate pair
                   if (sgp == null)
                       sgp = new Surrogate.Parser();
                   int uc = sgp.parse(c, sa, sp, sl);
                   if (uc < 0) {
                       updatePositions(src, sp, dst, dp);
                       return sgp.error();
                   }
                   if (dl - dp < 4)
                       return overflow(src, sp, dst, dp);
                   da[dp++] = (byte)(0xf0 | ((uc >> 18)));
                   da[dp++] = (byte)(0x80 | ((uc >> 12) & 0x3f));
                   da[dp++] = (byte)(0x80 | ((uc >>  6) & 0x3f));
                   da[dp++] = (byte)(0x80 | (uc & 0x3f));
                   sp++;  // 2 chars
               } else {
                   // 3 bytes, 16 bits
                   if (dl - dp < 3)
                       return overflow(src, sp, dst, dp);
                   da[dp++] = (byte)(0xe0 | ((c >> 12)));
                   da[dp++] = (byte)(0x80 | ((c >>  6) & 0x3f));
                   da[dp++] = (byte)(0x80 | (c & 0x3f));
               }
               sp++;
           }
           updatePositions(src, sp, dst, dp);
           return CoderResult.UNDERFLOW;
}




UTF-8 编码与 GBK 和 GB2312 不同,不用查码表,所以在编码效率上 UTF-8 的效率会更好,所以在存储中文字符时 UTF-8 编码比较理想。




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0