Java IO笔记(StringReader/StringWriter)


(最近刚来到平台,以前在CSDN上写的一些东西,也在逐渐的移到这儿来,有些篇幅是很早的时候写下的,因此可能会看到一些内容杂乱的文章,对此深感抱歉,以下为正文)


正文

本篇将要讲述的是Java IO包中的StringReader和StringWriter类。这两个类都是Reader和Writer的装饰类,使它们拥有了对String类型数据进行操作的能力。

下面还是先附上源码,然后对其进行简单的分析:

StringReader.java

package java.io;
 
public class StringReader extends Reader {
 
    //内置了一个String类型的变量,用于存储读取的内容。因为Reader只需要读取无需对数据进行改变,所以此时一个String类型变量就已经足够了。
    private String str;
    //定义了3个int型变量,length表示读取的字符串数据的长度,next表示下一个要读取的位置,mark表示标记的位置。
    private int length;
    private int next = 0;
    private int mark = 0;
 
    /**
     * 一个带一个参数的构造方法,传入的参数是一个String类型数据,通过s初始化内置的str和length属性。
     */
    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");
    }
 
    /**
     * 每次读取一个字符的read方法,最终返回读取字符的int值。
     */
    public int read() throws IOException {
        synchronized (lock) {
        //进行操作前,确保当前流处于开启状态。
            ensureOpen();
        //如果读取的位置,超过了数据的总长度,那么直接返回-1,表示已无数据可读。
            if (next >= length)
                return -1;
        //正常情况下通过next索引结合String类型的charAt方法,来从str中取出对应的字符数据。
            return str.charAt(next++);
        }
    }
 
    /**
     * 每次读入多个字符的read方法,最终返回实际读取的字符个数。该方法有3个参数,第一个参数为一个字符数组,用于存储读取的数据,第二和第三个参数为一个int
     * 变量,分别为开始在数组中存储数据的起点和存储数据的长度。
     */
    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;
            }
        //如果下一个读取的位置超过了读取的数据的总长度,表示此时已经无数据可读,此时直接返回-1。
            if (next >= length)
                return -1;
        //定义了一个int型值n,用来接收length-next和len之间的较小值,一般情况下使用len即可,如果len长度超过了数据的总长度,那么就使用length-next的值。
            int n = Math.min(length - next, len);
        //使用String类的getChars方法,将指定str从next到next+n位置的数据拷贝到传入cbuf中,拷贝位置从off开始。
            str.getChars(next, next + n, cbuf, off);
        //数据读取拷贝完毕后,将下一个读取的位置向后移位n位,最后返回n,即实际读取的数据长度。
            next += n;
            return n;
        }
    }
 
    /**
     * 该方法用于跳过指定长度的数据。
     */
    public long skip(long ns) throws IOException {
        synchronized (lock) {
        //进行操作前先确定当前流是否处于开启状态。
            ensureOpen();
        //如果当前读取的位置已经位于读取数据的末尾或者已经超过了数据总长度,那么直接返回0,因为此时已经无法再跳过数据进行读取了。
            if (next >= length)
                return 0;
            //定义了一个long型数据n用来存放length-next和ns之间的较小值,一般情况下是ns起作用,如果ns超过了当前未读取的数据总长度,那么使用length-next。
            long n = Math.min(length - next, ns);
        //这里是为了处理传入的ns是负数的情况,当传入的值为负数时,此时读取位置应当向回移动,在上一布操作中如果传入的ns为负数的话,那么此时的n必定是ns
        //Math.max(-next,n)则保证了只有只有当读取位置大于回读的数量时才可以回读,所以最多之能回退到数据的起点位置。
            n = Math.max(-next, n);
        //下一次读取的位置移动n个位置,最终将n返回。
            next += n;
            return n;
        }
    }
 
    /**
     * 该方法用于判断当前流是否处于可读状态。
     */
    public boolean ready() throws IOException {
        synchronized (lock) {
        ensureOpen();
        return true;
        }
    }
 
    /**
     * 该方法用于判断当前流是否支持流标记功能。
     */
    public boolean markSupported() {
        return true;
    }
 
    /**
     * 该方法用于在指定位置留下流标记,与reset方法连用,可以试当前读取位置回退到在流中的标记位置
     */
    public void mark(int readAheadLimit) throws IOException {
    //对传入的参数进行安全检测,标记的位置不能小于0,否则抛出相应的异常。
        if (readAheadLimit < 0){
            throw new IllegalArgumentException("Read-ahead limit < 0");
        }
        synchronized (lock) {
        //在进行操作前,确定当前流处于开启状态。
            ensureOpen();
        //使用mark变量记录下当前读取的位置。
            mark = next;
        }
    }
 
    /**
     * 该方法用于将当前读取位置回退到流中的标记位置。
     */
    public void reset() throws IOException {
        synchronized (lock) {
        //在进行操作前,确定当前流是否处于开启状态。然后将当前读取位置回退到mark处。
            ensureOpen();
            next = mark;
        }
    }
 
    /**
     * close方法,关闭当前流,将内置的str指向null。
     */
    public void close() {
        str = null;
    }
}

StringWriter.java

package java.io;
 
public class StringWriter extends Writer {
    //内置了一个StringBuffer,因为这里牵扯到了数据的改变,所以简单的String类型并不能满足我们。
    private StringBuffer buf;
 
    /**
     * 一个不带参的构造函数,内部为buf进行了初始化,并将该缓存区作为了内置锁对象。
     */
    public StringWriter() {
        buf = new StringBuffer();
        lock = buf;
    }
 
    /**
     * 一个带一个参数的构造函数,传入的参数为一个int型值,该值决定了内置buf初始化时的容量大小。
     */
    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));
    }
 
    /**
     * 该方法基本等同于上面的write(String str)方法,可以将制定的字符序列写入流中。
     */
    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;
    }
 
    /**
     * 将内置缓存区中的数据装换为String类型并返回。
     */
    public String toString() {
        return buf.toString();
    }
 
    /**
     * 返回内置的StringBuffer对象。
     */
    public StringBuffer getBuffer() {
        return buf;
    }
 
    /**
     * 该方法本是将缓存中的数据强制写出,在本类是一个空实现。
     */
    public void flush() {
    }
 
    /**
     * 该方法本市用于关闭流及释放流相关联的系统资源,在本类是一个空实现。
     */
    public void close() throws IOException {
    }
 
}

通过上面对源码的简单分析,我们队StringReader和StringWriter有了初步的认识,下面通过一个简单的小例子来展示其用法。

package StringIO;
 
import java.io.StringReader;
import java.io.StringWriter;
 
public class StringIOTest {
    public static void main(String[] args) {
        try (StringReader sr = new StringReader("just a test~");
                StringWriter sw = new StringWriter()) {
            int c = -1;
            while((c = sr.read()) != -1){
                sw.write(c);
            }
            //这里展示了即使关闭了StringWriter流,但仍然能获取到数据,因为其close方法是一个空实现。
            sw.close();
            System.out.println(sw.getBuffer().toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }
}

刚开始我是比较奇怪这两个类为什么会存在的,因为这与直接使用String类来进行数据操作,后来在网上看到别人的解释,如果你遇到一个情景是你必须使用一个Reader或者Writer来作为参数传递参数,但你的数据源又仅仅是一个String类型数据,无需从文件中写出,那么此时就可以用到它们。并且值得注意的是StringWriter中,写入的数据只是存在于缓存中,并不会写入实质的存储介质之中。

以上为本篇的全部内容。

你可能感兴趣的:(Java IO笔记(StringReader/StringWriter))