java字符流读取中文文件问题

中文编码 GBK 用 2 个字节,UTF-8 用 3 个字节,
对一个只含有字符 "你" 的文件进行读取, 文件大小 3 字节。
FileReader.read() 读一个字符,返回 int,4 字节,装得下,
然后下一步就迷糊了,为啥强转 char 能得到 “你”?
char 不是两个字节嘛,“你” 是三个字节啊。

public class CharIO {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("a.txt");    // 你
        System.out.println(fr.getEncoding());       // UTF8
        System.out.println(new File("a.txt").length()); // 3

        System.out.println("你".getBytes(StandardCharsets.UTF_8).length);    // 3

        int ch = fr.read();
        System.out.println((char)ch);     // 你        
        
        for (byte b : BigInteger.valueOf(ch).toByteArray()) {
            System.out.println(b);      // 79 96
        }

        for (byte b : "你".getBytes()) {
            System.out.println(b);          // -28 -67 -96
        }
    }
}

最后从字节上看,个数和值也不一样啊,中文字符编码不应该是负数的嘛?

阅读 3k
2 个回答

char 是 utf16 编码的,如果你用 getBytes(UTF_16BE) 就是一样的了

0,你需要的知识储备,字符集? 字符编码? 我假设你已经知道了这些

1,对于BigInteger.valueOf(ch).toByteArray() 请查看java.math.BigInteger#toByteArray API文档说明,有详细介绍,它说明了返回的bytes跟平常的不同之处

2,那么现在就剩下,int,char,和文件字节的疑问了
3,fr.read 根据文件的编码格式读取文件中第一个字符,例如文件编码是utf8,由于utf8变长存储数据,因此读取的文件字节长度可能不一样,从1--4个字节不等,

 Unicode符号范围 | UTF-8编码方式 
      (十六进制) | (二进制) 
0000 0000 0000 007F 0xxxxxxx 127
0000 0080 0000 07FF 110xxxxx 10xxxxxx 2047
0000 0800 0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx 65535
0001 0000 0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 1114111

4,中文 你 字,Unicode值为:20320,十六进制为:4f60,二进制为:100 1111 0110 0000
由于 2047< 20320 <65535 ,所以 你 用utf8编码后为三个节 e4bda0(你可使用hex editor 查看这个文件)

5,java读取这个三个字节 e4bda0 ,对应十进制为 228 189 160,
用byte存溢出了,为 -100 -61 -32 二进制为(byte 8位,之前所有高位截取掉):10011100 11000011 11100000,
这8位二进制数中最高位1代表负数,所以变成 -28 -67 -96

6,对于4步骤中的Unicode 20320 如何变成文件字节 e4bda0
参考上面Unicode -> utf8 编码方式
20320 二进制为:100 1111 0110 0000
符合第三个规则来插入,取20320二进制从末尾取值依次插入,代替x

 100   11 1101   10 0000

1110xxxx 10xx xxxx 10xx xxxx
======= 插入代替x
11100100 1011 1101 1010 0000
最终结果 1110 0100 1011 1101 1010 0000 转换为十六进制为 e4bda0 ,所以文件字节为它

7,从Unicode字符 20320, 到使用utf8编码存储的文件字节 e4bda0,再到java getbytes 228 189 160,截取末尾8位,最高位标志位,最终输出为-28 -67 -96

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题