理解Java控制台输入奇数个中文乱码

    |     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控制台输入奇数个中文乱码
本文链接地址:https://ai.zhousir.top/?p=744
回复 取消