理解Java控制台输入奇数个中文乱码
周sir |
2015年6月2日 |
输入输出(IO)操作 |
0 条评论 | 2102
这是很常见的一段从键盘读入的 Java 代码。运行时只要输入的是中文,而且字符个数为奇数,最后一个字就会乱码。
BufferedReader reader = new BufferedReader(
new InputStreamReader(System.in));
String message;
while ((message = reader.readLine()) != null) {
System.out.println(message);
}
原因:项目默认字符编码是 UTF-8,控制台也跟着变成 UTF-8,而键盘输入流默认是 GBK。GBK 转 UTF-8 时就会出现「奇数个中文乱码」。
一、控制台侧的两种改法
-
把项目文件编码改成 GBK,控制台编码会跟着变成 GBK。
-
代码区域右键 → Run As → Run Configurations → Common → Console encoding。若是 UTF-8,选 Other;没有 GBK 选项时再手动指定。
二、为何只有奇数个中文乱码
GBK 转 UTF-8 时,奇数个中文会乱码,偶数个不会。三个中文、后面再跟奇数个中文都会出问题:
public static void encodeError() throws UnsupportedEncodingException {
String gbk = "我来了";
String utf8 = new String(gbk.getBytes("UTF-8"));
// 模拟 UTF-8 编码的网站显示
System.out.println(new String(utf8.getBytes(), "UTF-8"));
}
// 输出:我来??
public static void encodeError2() throws UnsupportedEncodingException {
String gbk = "今年是2011年";
String utf8 = new String(gbk.getBytes("UTF-8"));
System.out.println(new String(utf8.getBytes(), "UTF-8"));
}
// 输出:今年??011??
看字节就能定位最后一个字节被改掉了:
public static void analyze() throws UnsupportedEncodingException {
String gbk = "我来了";
String utf8 = new String(gbk.getBytes("UTF-8"));
for (byte b : gbk.getBytes("UTF-8")) {
System.out.print(b + " ");
}
System.out.println();
for (byte b : utf8.getBytes()) {
System.out.print(b + " ");
}
}
// -26 -120 -111 -26 -99 -91 -28 -70 -122
// -26 -120 -111 -26 -99 -91 -28 -70 63
上面一行才是正确的 UTF-8。下面一行最后一个字节变成 63(ASCII 的 ?),乱码就出在这里。
| 编码 |
一个中文占用 |
注意 |
| GBK |
2 字节 |
按双字节切字符 |
| UTF-8 |
3 字节 |
3 个中文共 9 字节,奇数 |
| ISO-8859-1 |
1 字节 1 字符 |
不会吞掉最后一个字节 |
调用 getBytes("UTF-8") 会把 GBK 的 2 字节扩成 UTF-8 的 3 字节。源文件若是 GBK,new String(utfBytes) 实际是按 GBK 每 2 字节切一刀;字节数为奇数时,最后 1 字节转不成合法字符,就被写成 ?(63)。
public static void analyze2() throws UnsupportedEncodingException {
String gbk = "我来了";
byte[] utfBytes = gbk.getBytes("UTF-8"); // 9 个字节
String utf8 = new String(utfBytes); // 问题就出在这
System.out.print(utf8);
}
// 鎴戞潵浜?
三、保证字节不被截断
getBytes(“UTF-8”) 之后,用 ISO-8859-1 建字符串:一个字节对应一个字符,最后一个字节不会错。
public static void correctEncode() throws UnsupportedEncodingException {
String gbk = "我来了";
String iso = new String(gbk.getBytes("UTF-8"), "ISO-8859-1");
for (byte b : iso.getBytes("ISO-8859-1")) {
System.out.print(b + " ");
}
System.out.println();
System.out.println(new String(iso.getBytes("ISO-8859-1"), "UTF-8"));
}
// -26 -120 -111 -26 -99 -91 -28 -70 -122
// 我来了
四、字符转字节对照
「陶」在 GBK 是 cc d5,在 UTF-8 是 e9 99 b6。用指定字符集还原就能得到原字:
byte[] b = new byte[]{(byte) 0xcc, (byte) 0xd5};
System.out.println(new String(b, "GBK"));
// 输出:陶
一句话总结:奇数个中文乱码是因为 UTF-8 字节数是奇数,却被默认 GBK 按双字节去切;用 ISO-8859-1 或统一编码才能保住最后一个字节。
转载请注明来源:理解Java控制台输入奇数个中文乱码
我是周sir,这是我的博客。致力于分享我学会的技术和优秀的文章。微信公众号 “周sir专栏” 欢迎大家关注。