17、FileReader的源码和使用方法详细分析(windows操作系统,JDK8)

一、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循环的流程图,如下所示: