Unicode 统一了所有字符的编码,包括中文,它算是一个字符集,用 2 字节大小对所有字符进行了唯一编码
但是 Unicode 没有规定如何存储,UTF-8 刚好规定了 Unicode 字符如何存储的问题。UTF-8 采用可变长编码,英文用 1 个字节,中文用 3 个字节
对于 UTF-8 编码中的任意字节 B:
如果 B 的第一位为 0,则 B 独立的表示一个字符 (ASCII 码) -> 0xxxxxxx
如果 B 的第一位为 1,第二位为 0,则 B 为一个多字节字符中的一个字节 (非 ASCII 字符) -> 10xxxxxx
如果 B 的前两位为 1,第三位为 0,则 B 为两个字节表示的字符中的第一个字节 -> 110xxxxx
如果 B 的前三位为 1,第四位为 0,则 B 为三个字节表示的字符中的第一个字节 -> 1110xxxx
如果 B 的前四位为 1,第五位为 0,则 B 为四个字节表示的字符中的第一个字节 -> 11110xxx
举个简单的例子,中文汉字「我」对应的 Unicode 编码:十进制: 25105;十六进制:0x6211;二进制:01100010 00010001
它在 UTF-8 编码下的存储结构为:十六进制:0xE68891;二进制:11100110 10001000 10010001
为了方便对比,写到一起:
x
1110xxxx 10xxxxxx 10xxxxxx
UTF-8: 11100110 10001000 10010001
Unicode: 0110 001000 010001
在学习字节流和字符流的时候,遇到过一个问题:为什么 I/O 流操作要分为字节流操作和字符流操作呢?
如果只有字符流操作,由于字符流是由 Java 虚拟机将字节流转换得到,这个过程比较耗时
如果只有字节流操作,如果不知道编码类型的话,很容易出现乱码问题
本篇文章就上面的第二点展开讨论为什么会出现乱码问题!!!
如果我们用字节流去读 UTF-8 编码的文件,那么每次只能一个字节一个字节的读。对于一个中文汉字需要读三次,每次读的结果如下:11100110、10001000、10010001
可以看到三次读出来的结果其实就是上面分析过的 UTF-8 编码下的存储结构,此时就会将每一次的结果转化成一个 ACSII 字符:
11100110 -> 230 -> æ
10001000 -> 136 ->
10010001 -> 145 ->
所以乱码就出现了!!另外可以从下面程序中得到验证:
x
public static void main(String[] args) {
// input.txt 中存储的就是一个中文汉字「我」
// FileInputStream 字节输入流,每次读一个字节
try (FileInputStream fis = new FileInputStream("input.txt")) {
System.out.println("Number of remaining bytes: " + fis.available());
int content;
while ((content = fis.read()) != -1) { // 读一个字节
System.out.println(content + " -> " + (char) content);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
如果我们改用字符输入流,它每次都读一个字符,而且已经自动处理了编码问题,具体可见下面程序:
x
public static void main(String[] args) {
// input.txt 中存储的就是一个中文汉字「我」
// FileReader 字符输入流,每次读一个字符
try (FileReader fileReader = new FileReader("input.txt")) {
int content;
while ((content = fileReader.read()) != -1) {
System.out.println(content + " -> " + (char) content);
System.out.println(Integer.toBinaryString(content));
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 输出
25105 -> 我
110001000010001
input.txt
文件中存储的结构是:11100110 10001000 10010001,不信的话可以用二进制文件查看器打开它
但程序输出的二进制内容不再是 UTF-8 编码下存储的内容,而是对其进行了解码,转换成了 Unicode 下的编码,所以最后才没有乱码
注意:字符输入流并非每次都读三个字节。如果一个字符只用一个字节存储,那么一次就读一个字节,如果一个字符用三个字节存储,那么一次就读三个字节,可以根据 UTF-8 编码判断每次读几个字节
总结:字节输入流每次读一个字节,字符输入流每次读一个字符,而且字符输入流还自动的对 UTF-8 解码成 Unicode