Java I/O体系之Writer和Reader详细讲解

介绍:

Writer是字符输出流的基类,Reader是字符输入流的基类。这两个类的实现类基本都是成对存在的,下面就成对讲解。

字节流与字符流区别请查看:字节流和字符流的区别

体系图:

在这里插入图片描述

对常见实现类API的讲解:

OutputStreamWriter和InputStreamReader:

这两个类是转换流类,OutputStreamWriter可以将字节输出流转换成字符输出流,InputStreamReader可以将字节输入流转换成字符输入流。

//以下是InputStreamReader类的API解析:

//构造器:

//传入一个字节输入流,就是把该输入流转换成字符输入流。
public InputStreamReader(InputStream in)

//传入一个字符输入流并编码编码规则。
public InputStreamReader(InputStream in, String charsetName)

//传入一个字符输入流并编码编码规则。
public InputStreamReader(InputStream in, Charset cs)

//获取该流是否已经准备好可以读取,当缓冲区数据不为空或者底层有数据可供读取
//(也就是没有达到文件尾)时返回true。
public boolean java.io.InputStreamReader.ready() throws java.io.IOException

/**
* 从缓冲区读取若干数据到char数组中。
* 参数:
* 1. char[]数组 缓冲区的数据会读取到该数组里面。
* 2. int : 读取到char[]数组的偏移量。
* 3. int : 要读取的数据的长度。
* 返回的是读取到的数据长度,-1表示文件尾。
*/
public int java.io.InputStreamReader.read(char[],int,int) throws java.io.IOException
/**
* 从缓冲区中读取一个字符。
* 返回值是读取到的数据,-1表示到达文件尾。
*/
public int java.io.InputStreamReader.read() throws java.io.IOException


//关闭流
public void java.io.InputStreamReader.close() throws java.io.IOException

//获取编码规则名称
public java.lang.String java.io.InputStreamReader.getEncoding()

//读取若干个字符到char数组中,若干个是指 char数组长度与缓冲区大小取小值。
public int java.io.Reader.read(char[]) throws java.io.IOException

//读取数据到charBuffer中。
public int java.io.Reader.read(java.nio.CharBuffer) throws java.io.IOException

//这个方法不可用,直接抛异常。
public void java.io.Reader.mark(int) throws java.io.IOException

//是否支持mark,总是返回false。
public boolean java.io.Reader.markSupported()

//这个方法不可用,直接抛异常。
public void java.io.Reader.reset() throws java.io.IOException

//过滤一定长度的数据,底层是使游标移动一定长度。
public long java.io.Reader.skip(long) throws java.io.IOException
//以下是OutputStreamWriter类的API解析:

//构造器:
//传入一个字节输出流,把这个字节输出流转成字节输入流
public OutputStreamWriter(OutputStream out) 

//传入一个字节输出流,并指定编码规则
public OutputStreamWriter(OutputStream out, String charsetName)

//传入一个字节输出流,并指定编码规则
public OutputStreamWriter(OutputStream out, Charset cs)

/**
*把char数组的数据写到缓冲区中。
*参数:
*1. char[] : 要写到缓冲区的字符数组。
*2. int off: char[] 的偏移量。
*3. int len: 写入数据的长度,写入的数据时chars[off]开始len长度的数据。
*/
public void java.io.OutputStreamWriter.write(char[],int,int) throws java.io.IOException

//把一个字符数据写到缓冲区,参数就是要写入的字符数据。
public void java.io.OutputStreamWriter.write(int) throws java.io.IOException

/**
* 把一个String对象写入到缓冲区(会转换成char数组的)
* 参数:
* 1. String : 要写入的字符串。
* 2. off:偏移量
* 3. len场地
*/
public void java.io.OutputStreamWriter.write(java.lang.String,int,int) throws java.io.IOException

//关闭流,伴随刷盘行为
public void java.io.OutputStreamWriter.close() throws java.io.IOException

//强制把缓冲区数据刷到存储设备上
public void java.io.OutputStreamWriter.flush() throws java.io.IOException

//获取该字符流的编码规则
public java.lang.String java.io.OutputStreamWriter.getEncoding()

//返回自身writer,可以链式调用writer.append("abcd").append("efg")
//进行向缓冲区添加字符
public java.io.Writer java.io.Writer.append(java.lang.CharSequence) throws java.io.IOException


//链式添加,可以指定开始位置和结束位置。
public java.io.Writer 
java.io.Writer.append(java.lang.CharSequence,int,int) throws java.io.IOException

//链式添加一个字符
public java.io.Writer java.io.Writer.append(char) throws java.io.IOException

//链式添加
public java.io.Writer java.io.Writer.append(java.lang.CharSequence) throws java.io.IOException


//向缓冲区写入多个字符
public void java.io.Writer.write(char[]) throws java.io.IOException

//向缓冲区写入字符
public void java.io.Writer.write(java.lang.String) throws java.io.IOException

以下是使用OutputStreamWriter 和 InputStreamReader将一个文件字母转换成大写,并生成另一个文件的demo:

public class StreamConvertToCharDemo {

    public static void main(String[] args) {
        try (InputStreamReader reader = new InputStreamReader(new FileInputStream("1.txt"));
        OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream("3.txt"))){
            int len = -1;
            while ((len = reader.read()) != -1){
                writer.write(Character.toUpperCase(len));
            }

        }catch (IOException e){

        }
    }
}

运行结果:
在这里插入图片描述
在这里插入图片描述

CharArrayWriter和CharArrayReader:

这个是字符数组的输出流和输入流,是纯内存的操作,不涉及到磁盘。主要用途是在要创建临时文件,数据网络传输等情况下可以避免访问磁盘,直接在内存操作,提高运行效率。该类也不涉及底层操作,纯java代码。这里直接就用源码来讲吧。

CharArrayReader:

public class CharArrayReader extends Reader {
    
    //缓冲区
    protected char buf[];

    //游标,也就是buf[]下一个要读取的字符的下标值。
    protected int pos;

    //标记为,重置时游标会重置到该值
    protected int markedPos = 0;

    //数据长度
    protected int count;

    //指定字符数组作为缓冲区
    public CharArrayReader(char buf[]) {
    	
        this.buf = buf;
        this.pos = 0;
        this.count = buf.length;
    }

    //指定buf字符缓冲区,并且指定传进来的字符数组的偏移量和长度
    public CharArrayReader(char buf[], int offset, int length) {
    	//参数校验
        if ((offset < 0) || (offset > buf.length) || (length < 0) ||
            ((offset + length) < 0)) {
            throw new IllegalArgumentException();
        }
        this.buf = buf;
        //将游标设为指定的偏移量
        this.pos = offset;
        //取偏移量+长度    和 传进来的buf数组的长度的较小值作为缓冲区的数据长度。
        this.count = Math.min(offset + length, buf.length);
        //将重置标记置为偏移量。
        this.markedPos = offset;
    }

    //判断流是否已经打开了,缓冲区为空表示未打开。抛出IOException
    private void ensureOpen() throws IOException {
        if (buf == null)
            throw new IOException("Stream closed");
    }

    //同步方法,读取一个字符
    public int read() throws IOException {
        synchronized (lock) {
        	//验证流是否已经打开
            ensureOpen();
            //判断是否到达文件尾
            if (pos >= count)
                return -1;
            else
            	//返回
                return buf[pos++];
        }
    }

    //读取多个字符到 b[]字符数组中,指定偏移量和读取长度。
    public int read(char b[], int off, int len) throws IOException {
    	//同步方法
        synchronized (lock) {
            ensureOpen();
            //边界检查
            if ((off < 0) || (off > b.length) || (len < 0) ||
                ((off + len) > b.length) || ((off + len) < 0)) {
                throw new IndexOutOfBoundsException();
            } else if (len == 0) {
            	//读取长度为0时也是直接返回0
                return 0;
            }

			//判断是否到达文件尾
            if (pos >= count) {
                return -1;
            }
            //如果读取的长度大于可读取长度,那就把读取长度设置为可读取长度。
            if (pos + len > count) {
                len = count - pos;
            }
            
            if (len <= 0) {
                return 0;
            }
            //进行复制
            System.arraycopy(buf, pos, b, off, len);
            pos += len;
            return len;
        }
    }

   	//跳过一定长度的数据,同步方法。
    public long skip(long n) throws IOException {
        synchronized (lock) {
            ensureOpen();
             //如果过滤的长度大于可读取长度,那就把过滤长度设置为可读取长度。
            if (pos + n > count) {
                n = count - pos;
            }
            if (n < 0) {
                return 0;
            }
            //直接游标+n过滤掉n个数据。
            pos += n;
            return n;
        }
    }

   	//判断是否可用,未到达文件尾就返回true。
    public boolean ready() throws IOException {
        synchronized (lock) {
            ensureOpen();
            return (count - pos) > 0;
        }
    }

   	//是否支持标记。总是true支持。
    public boolean markSupported() {
        return true;
    }

   	//标记当前游标,参数无作用。
    public void mark(int readAheadLimit) throws IOException {
        synchronized (lock) {
            ensureOpen();
            markedPos = pos;
        }
    }

    //重置游标到markedPos标记处。
    public void reset() throws IOException {
        synchronized (lock) {
            ensureOpen();
            pos = markedPos;
        }
    }

    //关闭流。将close置空。
    public void close() {
        buf = null;
    }
}

CharArrayWriter:

public
class CharArrayWriter extends Writer {
    
    //缓冲区
    protected char buf[];

    //缓冲区数据长度
    protected int count;

    //默认构造器,默认缓冲区大小为32
    public CharArrayWriter() {
        this(32);
    }

    //指定缓冲区大小的构造器
    public CharArrayWriter(int initialSize) {
        if (initialSize < 0) {
            throw new IllegalArgumentException("Negative initial size: "
                                               + initialSize);
        }
        buf = new char[initialSize];
    }

   //往缓冲区中写入一个字符
    public void write(int c) {
        synchronized (lock) {
            int newcount = count + 1;
            if (newcount > buf.length) {
            	//两倍扩容或者扩容到newcount 大小,具体看哪个大,取大的那个。
                buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
            }
            buf[count] = (char)c;
            count = newcount;
        }
    }

    /**
     * 往缓冲区写入任意个字符
     * @param c 要写入的数据
     * @param off      c字符数组的偏移量
     * @param len      要写入的数据长度
     */
    public void write(char c[], int off, int len) {
        if ((off < 0) || (off > c.length) || (len < 0) ||
            ((off + len) > c.length) || ((off + len) < 0)) {
            //边界检查
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return;
        }
        synchronized (lock) {
            int newcount = count + len;
            if (newcount > buf.length) {
            	//两倍扩容或者扩容到newcount 大小,具体看哪个大,取大的那个。
                buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
            }
            //将c数组中的指定数据复制到缓冲区中。
            System.arraycopy(c, off, buf, count, len);
            //更新缓冲区大小
            count = newcount;
        }
    }

    /**
     * 将字符串的一部分写入的缓冲区中,由off和len决定
     * @param  要写入的字符串
     * @param  偏移量
     * @param  长度
     */
    public void write(String str, int off, int len) {
        synchronized (lock) {
            int newcount = count + len;
            if (newcount > buf.length) {
                buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
            }
            str.getChars(off, off + len, buf, count);
            count = newcount;
        }
    }

    /**
     * 将缓冲区数据写入到其他字符输出流中
     *
     * @param out       要写入的字符输出流
     * 
     */
    public void writeTo(Writer out) throws IOException {
        synchronized (lock) {
            out.write(buf, 0, count);
        }
    }

    /**
     * 链式追加缓冲区,writer.append("dsds").append("ddd"),
     * 与StringBuilder的append方法类似。
     */
    public CharArrayWriter append(CharSequence csq) {
        String s = (csq == null ? "null" : csq.toString());
        write(s, 0, s.length());
        return this;
    }

    /**
     * 链式追加到缓冲区,可以指定追加内容的范围。
     */
    public CharArrayWriter append(CharSequence csq, int start, int end) {
        String s = (csq == null ? "null" : csq).subSequence(start, end).toString();
        write(s, 0, s.length());
        return this;
    }

    /**
     * 链式追加一个字符到缓冲区
     */
    public CharArrayWriter append(char c) {
        write(c);
        return this;
    }

    /**
    *重置缓冲区,实际上就是把缓冲区大小count 置0,重新写入。
     */
    public void reset() {
        count = 0;
    }

    /**
     * 返回缓冲区char数组。但不是返回缓冲区,而是将缓冲区0-count的数据返  
     * 回
     */
    public char toCharArray()[] {
        synchronized (lock) {
            return Arrays.copyOf(buf, count);
        }
    }

    /**
     * 返回缓冲区大小
     */
    public int size() {
        return count;
    }

    /**
     * 将缓冲区字符转化为String对象返回
     */
    public String toString() {
        synchronized (lock) {
            return new String(buf, 0, count);
        }
    }

    /**
     * 刷盘,纯内存操作,空方法
     */
    public void flush() { }

    /**
     * 关闭流,空方法。
     */
    public void close() { }

}

FilterWriter和FilterReader:

FilterWriter是字符输出流包装器的基类,FilterReader是字符输入流的包装器的基类,这两个是抽象类,对流进行包装的类可以继承该类。这两个类可以看成是辅助类。

FileWriter和FileReader:

FileWriter和FileReader是对磁盘文件操作的字符输出流和字符输入流,连通着磁盘与内存。

FileWriter继承自OutputStreamWriter,FileReader继承自InputStreamReader,除了写了一些构造器外,剩下的所有方法都没有重写,而是直接复用父类的方法,所以下面就讲讲构造器,具体api查看上面的OutputStreamWriter和InputStreamWriter即可。

以下是FileReader源码:



public class FileReader extends InputStreamReader {

   //参数传入的是文件路径
    public FileReader(String fileName) throws FileNotFoundException {
    	//也是调用父类的构造器,跟 new InputStreamReader(new FileInputStream(fileName)) 没区别。
        super(new FileInputStream(fileName));
    }

  	//直接传入文件
    public FileReader(File file) throws FileNotFoundException {
        super(new FileInputStream(file));
    }

   //传入FileDescriptor 对象。
    public FileReader(FileDescriptor fd) {
        super(new FileInputStream(fd));
    }

}

以下是FileWriter的源码:

public class FileWriter extends OutputStreamWriter {

  	//文件路径
    public FileWriter(String fileName) throws IOException {
        super(new FileOutputStream(fileName));
    }

   	//append表示是否进行追加还是重写文件
    public FileWriter(String fileName, boolean append) throws IOException {
        super(new FileOutputStream(fileName, append));
    }

    //直接传入文件
    public FileWriter(File file) throws IOException {
        super(new FileOutputStream(file));
    }

    //
    public FileWriter(File file, boolean append) throws IOException {
        super(new FileOutputStream(file, append));
    }

    
    public FileWriter(FileDescriptor fd) {
        super(new FileOutputStream(fd));
    }

}


BufferedWriter和BufferedReader:

BufferedWriter是Writer类的装饰器,BufferedReader是Reader类的装饰器,添加了缓冲区功能,提高读写的效率。

//写一个字符数组到缓冲区中,可以设置偏移量和要写入长度
public void java.io.BufferedWriter.write(char[],int,int) throws java.io.IOException

//写一个字符串到缓冲区中,可以设置偏移量和要写入长度
public void 
java.io.BufferedWriter.write(java.lang.String,int,int) throws java.io.IOException

//写一个字符到缓冲区
public void java.io.BufferedWriter.write(int) throws java.io.IOException

//关闭流,伴随刷盘操作
public void java.io.BufferedWriter.close() throws java.io.IOException

//刷盘,把缓冲区的数据强制刷到磁盘或其他设备
public void java.io.BufferedWriter.flush() throws java.io.IOException
//新建一行,其实就是写一个换行符到缓冲区中。
public void java.io.BufferedWriter.newLine() throws java.io.IOException

//链式添加,与StringBuilder类似。把CharSequence写到缓冲区,并可以指定开始和结束位置。
public java.io.Writer java.io.Writer.append(java.lang.CharSequence,int,int) throws java.io.IOException

//链式添加,把一个字符添加到缓冲区。
public java.io.Writer java.io.Writer.append(char) throws java.io.IOException

//链式添加。把CharSequence写到缓冲区。
public java.io.Writer java.io.Writer.append(java.lang.CharSequence) throws java.io.IOException


//把一个字符数组添加到缓冲区中
public void java.io.Writer.write(char[]) throws java.io.IOException

//把一个字符串添加到缓冲区中
public void java.io.Writer.write(java.lang.String) throws java.io.IOException

//判断流是否已经关闭
public boolean java.io.BufferedReader.ready() throws java.io.IOException

//从缓冲区读取一个字符。
public int java.io.BufferedReader.read() throws java.io.IOException
//从缓冲区读取若干个字符到char[] 数组中。
//第二个int参数:偏移量,读取缓冲区的数据到char数组的从该下标开始。
//第三个int参数:len ,读取数据的长度。
public int java.io.BufferedReader.read(char[],int,int) throws java.io.IOException
//关闭流
public void java.io.BufferedReader.close() throws java.io.IOException
//读取一行数据
//如果到达文件尾,则返回null。
public java.lang.String java.io.BufferedReader.readLine() throws java.io.IOException

//过滤掉一定字符长度的数据
public long java.io.BufferedReader.skip(long) throws java.io.IOException
//从缓冲区读取数据到char[]数组中。返回读取到的长度,文件尾-1.
public int java.io.Reader.read(char[]) throws java.io.IOException

//小demo

public class BufferedWriterReaderDemo {

    public static void main(String[] args) {

        try (BufferedReader reader = new BufferedReader(new FileReader("1.txt"));
        BufferedWriter writer = new BufferedWriter(new FileWriter("5.txt"))){

            String s = null;
            while ((s = reader.readLine())!=null){
                writer.write(s);
            }

        }catch (IOException e){

        }
    }
}

StringReader和StreamWriter:

这个类似于CharArrayWriter和CharArrayReader,不过StringReader和StreamWriter是对字符串的操作,CharArrayWriter和StreamWriter是对字符的操作。是纯内存的操作,不涉及到磁盘。主要用途是在要创建临时文件,数据网络传输等可以避免访问磁盘,直接在内存操作,提高运行效率。该类也不涉及底层操作,纯java代码。这里直接就用源码来讲:

StringWriter

public class StringWriter extends Writer {

	//底层是StringBuffer。也就是说是线程安全的,因为写入是添加的,所以避免
	//String对象加号添加,提高效率。
    private StringBuffer buf;

    
    public StringWriter() {
        buf = new StringBuffer();
        lock = buf;
    }

    //可以设定StringBuffer的初始值
    public StringWriter(int initialSize) {
        if (initialSize < 0) {
            throw new IllegalArgumentException("Negative buffer size");
        }
        buf = new StringBuffer(initialSize);
        lock = buf;
    }

    //往缓冲区中添加一个字符
    public void write(int c) {
        buf.append((char) c);
    }

  	//往缓冲区中添加一个字符数组,并可以指定偏移量和长度
    public void write(char cbuf[], int off, int len) {
        if ((off < 0) || (off > cbuf.length) || (len < 0) ||
            ((off + len) > cbuf.length) || ((off + len) < 0)) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return;
        }
        buf.append(cbuf, off, len);
    }

    //往缓冲区中添加一个字符串
    public void write(String str) {
        buf.append(str);
    }

   //往缓冲区中添加一个字符串,并可以指定偏移量和长度
    public void write(String str, int off, int len)  {
        buf.append(str.substring(off, off + len));
    }

   //链式添加
    public StringWriter append(CharSequence csq) {
        if (csq == null)
            write("null");
        else
            write(csq.toString());
        return this;
    }

    //链式添加,并可以指定开始和结束的偏移量
    public StringWriter append(CharSequence csq, int start, int end) {
        CharSequence cs = (csq == null ? "null" : csq);
        write(cs.subSequence(start, end).toString());
        return this;
    }

    //链式添加一个字符
    public StringWriter append(char c) {
        write(c);
        return this;
    }

   //获取缓冲区并转换成字符串
    public String toString() {
        return buf.toString();
    }

    //换区缓冲区
    public StringBuffer getBuffer() {
        return buf;
    }

    //刷盘,因为是纯内纯操作,所以空方法
    public void flush() {
    }

   //关闭流,空方法
    public void close() throws IOException {
    }

}

StringReader:

public class StringReader extends Reader {

//底层
    private String str;
    //长度
    private int length;
    //下一个读取的字符的游标
    private int next = 0;
    //重置时重置标记
    private int mark = 0;

    /**
     * 传入一个字符串
     */
    public StringReader(String s) {
        this.str = s;
        this.length = s.length();
    }

    //是否已经打开,也就是底层str为空时就抛出异常。
    private void ensureOpen() throws IOException {
        if (str == null)
            throw new IOException("Stream closed");
    }

    //读取一个字符
    public int read() throws IOException {
        synchronized (lock) {
            ensureOpen();
            if (next >= length)
                return -1;
             //读取下标字符
            return str.charAt(next++);
        }
    }

    /**
    * 读取字符串中的字符到字符数组中,并制定偏移量和读取数据长度,不是缓冲区的
    * 偏移量是读取到cbuf的偏移量。
    */
    public int read(char cbuf[], int off, int len) throws IOException {
        synchronized (lock) {
            ensureOpen();
            if ((off < 0) || (off > cbuf.length) || (len < 0) ||
                ((off + len) > cbuf.length) || ((off + len) < 0)) {
                throw new IndexOutOfBoundsException();
            } else if (len == 0) {
                return 0;
            }
            if (next >= length)
                return -1;
            int n = Math.min(length - next, len);
            str.getChars(next, next + n, cbuf, off);
            next += n;
            return n;
        }
    }

    /**
     * 跳过一定长度数据,返回跳过的长度。
     */
    public long skip(long ns) throws IOException {
        synchronized (lock) {
            ensureOpen();
            //达到尾,返回0.
            if (next >= length)
                return 0;
            // Bound skip by beginning and end of the source
            //取可读取长度和期望跳过长度的较小值
            long n = Math.min(length - next, ns);
            
            n = Math.max(-next, n);
            next += n;
            return n;
        }
    }

    /**
     * 流是否已经准备好
     */
    public boolean ready() throws IOException {
        synchronized (lock) {
        ensureOpen();
        return true;
        }
    }

   //重置游标到mark值
    public void reset() throws IOException {
        synchronized (lock) {
            ensureOpen();
            next = mark;
        }
    }

    /**
     * 关闭流,把缓冲区置空
     */
    public void close() {
        str = null;
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值