这是我参与新手入门的第一篇文章
在Java web程序中,经常会遇到乱码的问题,如果不搞清分清字符集和编码,就会遇到很多令人头痛的问题。
字符集
字符集是一个系统支持的所有抽象字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。它就是一张码表,它规定了文字与数字的一一对应关系。与计算机的内部表示没有必然的联系。
比如ASCII字符集,它规定了0~127这128个数字与哪些字符的对应关系。这就是一个字典表,我去查 0x41是大写字母 ‘A’, 0x61是小写字母’a’。这些都是在ascii码表里规定了的。
本文只关注一种字符集,Unicode字符集。Unicode编码系统为表达任意语言的任意字符而设计。它使用4字节的数字来表达每个字母、符号,或者表意文字(ideograph)。每个数字代表唯一的至少在某种语言中使用的符号。被几种语言共用的字符通常使用相同的数字来编码。每个字符对应一个数字,每个数字对应一个字符。
字符编码
字符编码(Character Encoding):是一套法则,使用该法则能够对自然语言的字符的一个集合(如字母表或音节表),与其他东西的一个集合进行配对。即在符号集合与数字系统之间建立对应关系,它是信息处理的一项基本技术。通常人们用符号集合来表达信息。而以计算机为基础的信息处理系统则是利用元件不同状态的组合来存储和处理信息的。元件不同状态的组合能代表数字系统的数字,因此字符编码就是将符号转换为计算机可以接受的数字系统的数,称为数字代码。
这里主要看一下UTF-8编码,UTF-8的编码方法比较简单,大致可以这么描述:
-
0~127,直接使用原码。比如0x61,在UTF-8里,就使用一个字节表示。其值就是0x61。
-
两字节的UTF-8,都编码成这个样子:110XXXXX 10XXXXXX。这就是说,如果超过了127,就不能再使用一个字节进行编码了,要扩展成两字节编码。而两字节编码的情况呢,其中5位是固定的。剩下的11位可以用来编码。
第一字节的前三位是110,第二字节的前两位是10,比如128,它的二进制是1000 0000,那么它的UTF-8就必须是两字节的。把后面的6个0,编码到第二字节,把前面的10,编码到第一字节,那么结果就是:
110 00010,10 000000
- 三字节的UTF-8的编码格式是这样的:1110 XXXX, 10 XXXXXX, 10 XXXXXX,可以编码16位数字,涵盖了大部分的常用汉字。
直接使用字节流从控制台读入UTF-8编码的汉字时,读入的就是上面的三个字节,也就是原始的UTF-8编码。但如果使用字符流去读的话,得到的就是unicode码。这中间一定发现了什么事情。
源码如下:
public int decode(byte[] sa, int sp, int len, char[] da) {
final int sl = sp + len;
int dp = 0;
int dlASCII = Math.min(len, da.length);
ByteBuffer bb = null; // only necessary if malformed
// ASCII only optimized loop
while (dp < dlASCII && sa[sp] >= 0)
da[dp++] = (char) sa[sp++];
while (sp < sl) {
int b1 = sa[sp++];
if (b1 >= 0) {
// 1 byte, 7 bits: 0xxxxxxx
da[dp++] = (char) b1;
} else if ((b1 >> 5) == -2 && (b1 & 0x1e) != 0) {
// 2 bytes, 11 bits: 110xxxxx 10xxxxxx
if (sp < sl) {
int b2 = sa[sp++];
....// 这里非法检查,略去。
da[dp++] = (char) (((b1 << 6) ^ b2)^
(((byte) 0xC0 << 6) ^
((byte) 0x80 << 0)));
}
continue;
}
if (malformedInputAction() != CodingErrorAction.REPLACE)
return -1;
da[dp++] = replacement().charAt(0);
return dp;
} else if ((b1 >> 4) == -2) {
// 3 bytes, 16 bits: 1110xxxx 10xxxxxx 10xxxxxx
if (sp + 1 < sl) {
int b2 = sa[sp++];
int b3 = sa[sp++];
if (isMalformed3(b1, b2, b3)) {
.....
} else {
char c = (char)((b1 << 12) ^
(b2 << 6) ^
(b3 ^
(((byte) 0xE0 << 12) ^
((byte) 0x80 << 6) ^
((byte) 0x80 << 0))));
.......
}
continue;
}
if (malformedInputAction() != CodingErrorAction.REPLACE)
return -1;
if (sp < sl && isMalformed3_2(b1, sa[sp])) {
da[dp++] = replacement().charAt(0);
continue;
}
da[dp++] = replacement().charAt(0);
return dp;
} else if ((b1 >> 3) == -2) {
// 4 bytes, 21 bits: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
if (sp + 2 < sl) {
int b2 = sa[sp++];
int b3 = sa[sp++];
int b4 = sa[sp++];
int uc = ((b1 << 18) ^
(b2 << 12) ^
(b3 << 6) ^
(b4 ^
(((byte) 0xF0 << 18) ^
((byte) 0x80 << 12) ^
((byte) 0x80 << 6) ^
((byte) 0x80 << 0))));
if (isMalformed4(b2, b3, b4) ||
// shortest form check
!Character.isSupplementaryCodePoint(uc)) {
.......
} else {
da[dp++] = Character.highSurrogate(uc);
da[dp++] = Character.lowSurrogate(uc);
}
continue;
}
if (malformedInputAction() != CodingErrorAction.REPLACE)
return -1;
b1 &= 0xff;
if (b1 > 0xf4 ||
sp < sl && isMalformed4_2(b1, sa[sp] & 0xff)) {
da[dp++] = replacement().charAt(0);
continue;
}
sp++;
if (sp < sl && isMalformed4_3(sa[sp])) {
da[dp++] = replacement().charAt(0);
continue;
}
da[dp++] = replacement().charAt(0);
return dp;
} else {
if (malformedInputAction() != CodingErrorAction.REPLACE)
return -1;
da[dp++] = replacement().charAt(0);
}
}
return dp;
}
}
复制代码
代码中除了大量的非法检查,就是进行移位拼装。
总结
编码是依赖于字符集的,就像代码中的接口实现依赖于接口一样,一个字符集可以有多个编码实现,就像一个接口可以有多个实现类一样。