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编码),内容如下:
image

程序运行结果,如下所示:
image

整个执行过程分为以下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)…等属性,分别如下:
image
image

②、然后第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系统中的换行符),如下所示:
image

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,如下所示:
image
image
image

c、返回给使用者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,如下所示:
image
image
image

b、返回给使用者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的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循环的流程图,如下所示:
image

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐