一、FileReader------对InputStreamReader的封装
如果说InputStreamReader是FileInputStream和FileReader之间的桥梁,那么FileReader就是对InputStreamReader的封装,从FileReader的源码来看,除了将InputStreamReader(实际是StreamDecoder)中需要的字节输入流InputStream in 指定为固定的FileInputStream对象和将InputStreamReader(实际是StreamDecoder)中的解码字符集(解码方案)设置为UTF-8以外,FileReader没有做其它任何事情。
InputStreamReader和StreamDecoder的相关内容,请看我的另一篇博客:16、InputStreamReader和StreamDecoder的源码和使用方法详细分析(windows操作系统,JDK8)
FileReader的UML关系图,如下所示:
FileReader.class的源码如下:
package java.io;
public class FileReader extends InputStreamReader {
//构造函数,通过文件名字符串(这个字符串必须是文件的绝对地址路径)创建一个FileInputStream类型字节输入流的InputStreamReader对象
public FileReader(String fileName) throws FileNotFoundException {
super(new FileInputStream(fileName));
}
//构造函数,通过File对象创建一个FileInputStream类型字节输入流的InputStreamReader对象
public FileReader(File file) throws FileNotFoundException {
super(new FileInputStream(file));
}
//构造函数,通过FileDescriptor对象创建一个FileInputStream类型字节输入流的InputStreamReader对象
public FileReader(FileDescriptor fd) {
super(new FileInputStream(fd));
}
}
二、FileReader的read(char cbuf\[\])函数的执行过程
Reader的相关内容,请看我的另一篇博客:14、Reader的源码、FilterReader源码、PushbackReader源码(windows操作系统,JDK8)
public abstract class Reader implements Readable, Closeable {
public int read(char cbuf[]) throws IOException {
return read(cbuf, 0, cbuf.length);
}
abstract public int read(char cbuf[], int off, int len) throws IOException;
}
public class InputStreamReader extends Reader {
public int read(char cbuf[], int offset, int length) throws IOException {
return sd.read(cbuf, offset, length);
}
}
public class StreamDecoder extends Reader {
//如果有足够多字符的话,一次性读取length个字符到字符数组char[] cbuf的[offset,offset+length)索引位置,并返回实际读取到的字符数量
//函数内部做了线程同步
public int read(char[] cbuf, int offset, int length) throws IOException {
synchronized (lock) {
//先在这个函数的调用栈上复制offset为off、length为len
int off = offset;
int len = length;
ensureOpen();
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
((off + len) > cbuf.length) || ((off + len) < 0)) {//char[]数组cbuf的[offset,offset+length)(左闭右开)索引位置是否有越界的检查
throw new IndexOutOfBoundsException();//越界的话,抛出一个IndexOutOfBoundsException
}
if (len == 0)
return 0;//如果len==0,结束本次函数调用,并返回0
//实际读取到的字符数量
int n = 0;
// 如果char leftoverChar变量中有被赋值(缓存)的字符char,则boolean haveLeftoverChar变量为true
if (haveLeftoverChar) {
// 将char leftoverChar变量中被赋值(缓存)的字符char先放到字符数组char[] cbuf的off(offset)索引位置
cbuf[off] = leftoverChar;
off++; len--;//然后将off(offset)索引位置右移1位,将len-=1
haveLeftoverChar = false;//并把boolean haveLeftoverChar标记改为false表示char leftoverChar变量中有被赋值(缓存)的字符了
n = 1;//设置n=1
if ((len == 0) || !implReady())
// Return now if this is all we can produce w/o blocking
//如果这已经是StreamDecoder在不阻塞的情况下能读取到字符数组char[] cbuf中的全部字符了,就立即返回。
return n;
}
if (len == 1) {
//如果此时len==1,则表示接下来只需要向字符数组char[] cbuf中再读取1个字符,那么直接调用read0()函数即可,此时len==1分为以下2中情况:
//场景一:要读取到字符数组char[] cbuf中的字符数量length=2,同时char leftoverChar变量中被赋值(缓存)了1个字符
//场景二:要读取到字符数组char[] cbuf中的字符数量length=1,并且char leftoverChar变量中没有缓存字符
int c = read0();//read0()函数一次只能读取1个字符
if (c == -1)//c==-1表示从已经从read0()函数中读取不到任何字符了
return (n == 0) ? -1 : n;//返回实际读取到字符数组char[] cbuf中的字符数量n或者-1
cbuf[off] = (char)c;//如果还能从read0()函数中读取到字符,则将读取到的这个字符保存到字符数组char[] cbuf中
return n + 1;//累加1到实际读取到字符数组char[] cbuf中的字符数量n上面,并返回
}
// Read remaining characters
//执行完上面的代码到这里的时候,off和len有以下2种可能:
//可能1:char leftoverChar变量中没有被赋值(缓存)的字符char,并且len>1(要填充到字符数组char[] cbuf中的字符数量length>1);
//可能2:char leftoverChar变量中有被赋值(缓存)的字符char,并且len>2(要填充到字符数组char[] cbuf中的字符数量length>2)。
//那么执行implRead()函数向字符数组char[] cbuf的[off,off + len)索引位置填充len个字符,并返回实际填充的字符数量nr,举例如下:
//假如当int offset的实参为0,int length的实参为5时,如果之前满足可能1,则此时[off,off + len)为[0,5),返回5或者-1,如果之前满足可能2,则此时[off,off + len)为[1,5),返回4或者-1
int nr = implRead(cbuf, off, off + len);
// At this point, n is either 1 if a leftover character was read,
// or 0 if no leftover character was read. If n is 1 and nr is -1,
// indicating EOF, then we don't return their sum as this loses data.
//此时nr有以下2种可能得值,-1或者实际向字符数组char[] cbuf的[off,off + len)索引位置填充的字符数量nr,当nr=-1时,n只有以下2种可能:
//可能a:之前char leftoverChar变量中没有被赋值(缓存)的字符char,n=0,此时return -1
//可能b:之前char leftoverChar变量中有被赋值(缓存)的字符char,n=1,此时return 1
//当nr≠=-1时,表示的确向字符数组char[] cbuf的[off,off + len)索引位置填充了nr个字符,return n+nr(0+nr或者1+nr)
return (nr < 0) ? (n == 1 ? 1 : nr) : (n + nr);
}
}
//如果有足够多字符的话,一次性填充end-off个字符到字符数组char[] cbuf的[off,end)索引位置,并返回实际填充到字符数组char[] cbuf中的字符数量
int implRead(char[] cbuf, int off, int end) throws IOException {
//要填充到字符数组char[] cbuf中的字符数量必须>1
assert (end - off > 1);
//将字符数组char[] cbuf包装到字符缓冲区CharBuffer cb中,包装到缓冲区之后,字符缓冲区CharBuffer cb中修改的内容会同步修改到字符数组char[] cbuf中
//可以将CharBuffer对象理解为一个数组,管理的索引位置为[off,end)区间的end - off个字符
CharBuffer cb = CharBuffer.wrap(cbuf, off, end - off);
if (cb.position() != 0) {
//将字符缓冲区CharBuffer cb管理的索引区间和要填充的字符数组char[] cbuf进行对齐,确保cb[0]=cbuf[0ff]
cb = cb.slice();
}
//eof=false表示还没有读到字节输入流(或者叫文件)的末尾,字节输入流或者文件中还有字节可以提供给StreamDecoder来进行转换
boolean eof = false;
for (;;) {
//从字节缓冲区ByteBuffer bb中解码尽可能多的字节,将结果写入给定的字符缓冲区CharBuffer cb中,也就是字符数组char[] cbuf中用于读取字符
CoderResult cr = decoder.decode(bb, cb, eof);
if (cr.isUnderflow()) {
if (eof)
break;
if (!cb.hasRemaining())
//检查字符缓冲区CharBuffer cb中是否还有剩余空间,若没有则跳出循环
break;
if ((cb.position() > 0) && !inReady())
//字符缓冲区CharBuffer cb存在内容时,跳出循环,以便返回读取到的字符数
break; // Block at most once
//读取字节到字节缓冲区ByteBuffer bb
int n = readBytes();
if (n < 0) {
//若读取字节到字节缓冲区ByteBuffer bb时返回了-1,代表着已到了字节输入流(或者叫文件)的末尾,则不会有其它字节输入了
eof = true;
if ((cb.position() == 0) && (!bb.hasRemaining()))
//如果此时字符缓冲区CharBuffer cb和字节缓冲区ByteBuffer bb中都没有数据了,则跳出循环
break;
}
continue;
}
if (cr.isOverflow()) {
//当从字节缓冲区ByteBuffer bb中解码的字节太多导致溢出时,字符缓冲区CharBuffer cb必须有解码的字符,否则这里的断言就会报错
assert cb.position() > 0;
//溢出时也跳出循环
break;
}
cr.throwException();
}
if (eof) {
// ## Need to flush decoder
//如果已经到了字节输入流(或者叫文件)的末尾,则重置解码器,清除所有内部状态。
decoder.reset();
}
if (cb.position() == 0) {
if (eof) {
return -1;
}
assert false;
}
//返回字符缓冲区CharBuffer cb的当前位置,代表着实际读取或者填充到字符数组char[] cbuf中的字符数量
return cb.position();
}
//最终调用的方法,利用底层字节输入流,读取字节到字节缓冲区ByteBuffer bb中
private int readBytes() throws IOException {
bb.compact();
try {
if (ch != null) {
// Read from the channel
int n = ch.read(bb);
if (n < 0)
return n;
} else {
// Read from the input stream, and then update the buffer
// 从输入流中读取,然后更新字节缓冲区ByteBuffer bb
int lim = bb.limit();
int pos = bb.position();
assert (pos <= lim);
int rem = (pos <= lim ? lim - pos : 0);
int n = in.read(bb.array(), bb.arrayOffset() + pos, rem);
if (n < 0)
return n;
if (n == 0)
throw new IOException("Underlying input stream returned zero bytes");
assert (n <= rem) : "n = " + n + ", rem = " + rem;
bb.position(pos + n);
}
} finally {
//即使抛出 IOException 也要翻转,否则字节缓冲区ByteBuffer会有问题
bb.flip();//反转字节缓冲区中的所有字节
}
//计算读取到字节缓冲区ByteBuffer中的字节数量,并返回
int rem = bb.remaining();
assert (rem != 0) : rem;
return rem;
}
}
如果使用者用的是文件名字符串(这个字符串必须是文件的绝对地址路径)构造了FileReader的对象(解码字符集或者说解码方案默认为UTF-8),并循环调用read(char cbuf\[\])函数来向使用者的字符数组中填充字符,如下所示:
package com.chelong.bio;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.Charset;
public class FileReaderTest {
public static void main(String[] args) throws IOException {
char[] chars = new char[12];
System.out.println("jvm默认使用的编码字符集(编码方案):" + Charset.defaultCharset().name());
FileReader fr = new FileReader("D:\\nio-data.txt");
while (fr.read(chars) != -1) {
for (int i = 0; i < chars.length; i++) {
System.out.print(chars[i]);
}
}
fr.close();
}
}
我的windows操作系统的D盘根目录下有nio-data.txt文件(该文件必须也为UTF-8编码),内容如下:
程序运行结果,如下所示:
整个执行过程分为以下4步
①、先执行StreamDecoder.class::forInputStreamReader()这个工厂函数,构造一个StreamDecoder对象,StreamDecoder对象中的Charset cs、CharsetDecoder decoder(DoubleByte.class::Decoder.class类型)、ByteBuffer bb(容量为8192)、InputStream in(使用者创建的FileInputStream对象)、ReadableByteChannel ch(在InputStreamReader创建的StreamDecoder中为null)...等属性,分别如下:
②、然后第1次进入使用者的while循环,执行InputStreamReader.class::read()函数,实际上执行的是StreamDecoder.class::read()函数,此步又分为以下3步:
a、先执行StreamDecoder.class::readBytes()函数向ByteBuffer bb(容量为8192)中填充48个字节,这48个字节对应"hello,世界\r\nhello,世界,我将征服你"这24个UTF-8编码的字符(\r\n是windows系统中的换行符),如下所示:
b、利用
CoderResult cr = decoder.decode(bb, cb, eof);
这行代码将ByteBuffer bb中0,18)索引位置上的字节转换为12个字符分别为:"h"、"e"、"l"、"l"、"o"、","(中文逗号)、"世"、"界"、"\\r"、"\\n"、"h"、"e",填充到给定的字符缓冲区CharBuffer cb中,也就是字符数组char\[ cbuf中用于读取字符,并修改ByteBuffer bb(容量为8192)的position(可以理解为左指针)=18,如下所示:
c、返回给使用者cb.position()(也就是12),并且使用者构造的字符数组char\[\] chars(也就是步骤b中的字符数组char\[\] cbuf)已经填充了12个字符,然后使用者在while循环的内层for循环中遍历并在控制台输出char\[\] chars(也就是步骤b中的字符数组char\[\] cbuf)中的字符,如下所示:
第1次进入使用者的while循环的流程图,如下所示:
③、然后第2次进入使用者的while循环,执行InputStreamReader.class::read()函数,实际上执行的是StreamDecoder.class::read()函数,此步又分为以下2步:
a、利用
CoderResult cr = decoder.decode(bb, cb, eof);
这行代码将ByteBuffer bb中18,48)索引位置上的字节转换为12个字符分别为:"l"、"l"、"o"、","(中文逗号)、"世"、"界"、","(中文逗号)、"我"、"将"、"征"、"服"、"你",填充到给定的字符缓冲区CharBuffer cb中,也就是字符数组char\[ cbuf中用于读取字符,并修改ByteBuffer bb(容量为8192)的position(可以理解为左指针)=48,如下所示:
b、返回给使用者cb.position()(也就是12),并且使用者构造的字符数组char\[\] chars(也就是步骤b中的字符数组char\[\] cbuf)已经填充了12个字符,然后使用者在while循环的内层for循环中遍历并在控制台输出char\[\] chars(也就是步骤a中的字符数组char\[\] cbuf)中的字符,如下所示:
第2次进入使用者的while循环的流程图,如下所示:
④、接下来第3次进入使用者的while循环,执行InputStreamReader.class::read()函数,实际上执行的是StreamDecoder.class::read()函数,此步又分为以下3步:
a、执行
CoderResult cr = decoder.decode(bb, cb, eof);
的时候无法将ByteBuffer bb中的任何字节转换为字符并填充到给定的字符缓冲区CharBuffer cb中,也就是字符数组char\[\] cbuf中,然后执行
int n = readBytes();
函数时返回了-1,此时字节缓冲区ByteBuffer bb中的内容,如下所示:
虽然节缓冲区ByteBuffer bb的[0,48)索引位置仍然有值(这些值就是"hello,世界\r\nhello,世界,我将征服你"(\r\n是windows操作系统中的换行符)的GB2312编码),但是节缓冲区ByteBuffer bb的position=limit=0,这表示这个节缓冲区ByteBuffer bb已经不能再使用了。
b、然后认为这个字节输入流InputStream in(或者叫文件)已经读取完了(没有字节可以提供使用了),并将StreamDecoder中真正用来将ByteBuffer bb中的字节解码成不同字符的工具CharsetDecoder decoder重置
decoder.reset();
然后一层一层的返回-1给使用者;
c、使用者的while循环在执行fr.read(chars)的时候
while (fr.read(chars) != -1) {
获得的返回值为-1,经过判断,跳出这个while循环,然后关闭所有的流。第3次进入使用者的while循环的流程图,如下所示: