)
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操作系统JDK8public 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,offsetlength)索引位置并返回实际读取到的字符数量//函数内部做了线程同步public int read(char[] cbuf, int offset, int length) throws IOException {synchronized (lock) {//先在这个函数的调用栈上复制offset为off、length为lenint off offset;int len length;ensureOpen(); if ((off 0) || (off cbuf.length) || (len 0) || ((off len) cbuf.length) || ((off len) 0)) {//char[]数组cbuf的[offset,offsetlength)左闭右开索引位置是否有越界的检查 throw new IndexOutOfBoundsException();//越界的话抛出一个IndexOutOfBoundsException } if (len 0) return 0;//如果len0结束本次函数调用并返回0 //实际读取到的字符数量 int n 0; // 如果char leftoverChar变量中有被赋值缓存的字符char则boolean haveLeftoverChar变量为true if (haveLeftoverChar) { // 将char leftoverChar变量中被赋值缓存的字符char先放到字符数组char[] cbuf的offoffset索引位置 cbuf[off] leftoverChar; off; len--;//然后将offoffset索引位置右移1位将len-1 haveLeftoverChar false;//并把boolean haveLeftoverChar标记改为false表示char leftoverChar变量中有被赋值缓存的字符了 n 1;//设置n1 if ((len 0) || !implReady()) // Return now if this is all we can produce w/o blocking //如果这已经是StreamDecoder在不阻塞的情况下能读取到字符数组char[] cbuf中的全部字符了就立即返回。 return n; } if (len 1) { //如果此时len1则表示接下来只需要向字符数组char[] cbuf中再读取1个字符那么直接调用read0()函数即可此时len1分为以下2中情况 //场景一要读取到字符数组char[] cbuf中的字符数量length2同时char leftoverChar变量中被赋值缓存了1个字符 //场景二要读取到字符数组char[] cbuf中的字符数量length1并且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种可能 //可能1char leftoverChar变量中没有被赋值缓存的字符char并且len1要填充到字符数组char[] cbuf中的字符数量length1 //可能2char leftoverChar变量中有被赋值缓存的字符char并且len2要填充到字符数组char[] cbuf中的字符数量length2。 //那么执行implRead()函数向字符数组char[] cbuf的[off,off len)索引位置填充len个字符并返回实际填充的字符数量nr举例如下 //假如当int offset的实参为0int 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 dont return their sum as this loses data. //此时nr有以下2种可能得值-1或者实际向字符数组char[] cbuf的[off,off len)索引位置填充的字符数量nr当nr-1时n只有以下2种可能 //可能a之前char leftoverChar变量中没有被赋值缓存的字符charn0此时return -1 //可能b之前char leftoverChar变量中有被赋值缓存的字符charn1此时return 1 //当nr≠-1时表示的确向字符数组char[] cbuf的[off,off len)索引位置填充了nr个字符return nnr0nr或者1nr 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(); } //eoffalse表示还没有读到字节输入流或者叫文件的末尾字节输入流或者文件中还有字节可以提供给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编码内容如下image程序运行结果如下所示image整个执行过程分为以下4步①、先执行StreamDecoder.class::forInputStreamReader()这个工厂函数构造一个StreamDecoder对象StreamDecoder对象中的Charset cs、CharsetDecoder decoderDoubleByte.class::Decoder.class类型、ByteBuffer bb容量为8192、InputStream in使用者创建的FileInputStream对象、ReadableByteChannel ch在InputStreamReader创建的StreamDecoder中为null…等属性分别如下imageimage②、然后第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系统中的换行符如下所示imageb、利用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如下所示imageimageimagec、返回给使用者cb.position()也就是12并且使用者构造的字符数组char[] chars也就是步骤b中的字符数组char[] cbuf已经填充了12个字符然后使用者在while循环的内层for循环中遍历并在控制台输出char[] chars也就是步骤b中的字符数组char[] cbuf中的字符如下所示image第1次进入使用者的while循环的流程图如下所示image③、然后第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如下所示imageimageimageb、返回给使用者cb.position()也就是12并且使用者构造的字符数组char[] chars也就是步骤b中的字符数组char[] cbuf已经填充了12个字符然后使用者在while循环的内层for循环中遍历并在控制台输出char[] chars也就是步骤a中的字符数组char[] cbuf中的字符如下所示image第2次进入使用者的while循环的流程图如下所示image④、接下来第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中的内容如下所示image虽然节缓冲区ByteBuffer bb的[0,48)索引位置仍然有值这些值就是“hello世界\r\nhello世界我将征服你”\r\n是windows操作系统中的换行符的GB2312编码但是节缓冲区ByteBuffer bb的positionlimit0这表示这个节缓冲区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循环的流程图如下所示image