前言
今天看到这么一个问题:FileReader是如何读取一个中文的,烦请大神分析一下?
,于是写篇文章来分析一下 0.0
本回答仅对 Oracle JDK 1.8.0 负责。
初步分析
首先看一下FileReader
的源代码:
1 | // java.io.FileReader.java (已省略文档注释) |
实际看一下FileReader
的源代码就会发现,它其实只是简单的包装了一下InputStreamReader
,简单的帮你将文件转换成了FileInputStream
传递给了InputStreamReader
。
而中文其实就是一个char
,那么问题实际上就是:InputStreamReader
是如何读取char
的。
让我们来看一下InputStreamReader
的read
是怎么做的(只有char[]
参数的read
方法其实是包装了这个方法):
1 | // int java.io.InputStreamReader.read(char, int, int) |
我们可以看到它只是对sd.read
的包装,那么我们再来看看这个sd
是个什么鬼:
1 | // int java.io.InputStreamReader.java (已省略文档注释) |
可以看到sd
是StreamDecoder
的实例,顺便贴了被FileReader
调用的构造函数,可以看到就是在这里实例化了sd
。
那么我们再去瞅瞅这个StreamDecoder.read
,这货是sun
包的,没有直接提供源码。
但我们不怕,可以反编译,也可以看OpenJDK
的代码,为了方便看,这里选择了看OpenJDK
的代码。
StreamDecoder
首先来看看它的read
方法:
1 | public int read(char cbuf[], int offset, int length) throws IOException { |
可以看到前面都是针对单个字符的处理,实际读取多个字符的是后面的implRead
,而之前也有个read0
可以读取一个字符。
查看源代码发现,read0
实际上还是对read
的包装,最后还是会调用到implRead
,所以就不浪费时间了,直接看implRead
吧:
1 | int implRead(char[] cbuf, int off, int end) throws IOException { |
可以看到,最关键的解码是由decoder.decode
来完成的,那么这个decoder
是啥呢,我们来瞅瞅:
1 | private CharsetDecoder decoder; |
通过看源代码可以发现,这个东东是由构造函数去初始化的,回顾下InputStreamReader
的构造函数,构造StreamDecoder
的方法其实是这个:
1 | sd = StreamDecoder.forInputStreamReader(in, this, (String)null); |
OK,来瞅瞅这个forInputStreamReader
:
1 | public static StreamDecoder forInputStreamReader(InputStream in, Object lock, String charsetName) throws UnsupportedEncodingException { |
charsetName
参数传进来的就是null
,所以实际会取Charset.defaultCharset().name()
。
查看源码可知,这个编码默认是System.getProperty("file.encoding")
,如不支持则会使用UTF-8
。
这里我就不再去分析Charset.newDecoder
的做了什么了,直接拿一段简单的代码执行一下就知道了:
1 | System.out.println(Charset.defaultCharset().newDecoder().getClass()); |
OK,现在我们知道了,解码工作实际是由sun.nio.cs.ext.DoubleByte$Decoder
来完成的。
这个类里面实现了对双字节字符的解码,这个我就不写了,有兴趣可以自己去瞅瞅:
sun.nio.cs.ext.DoubleByte$Decoder
结论
FileReader
是通过System.getProperty("file.encoding")
对应的解码器来读取中文的。