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中,写入的数据只是存在于缓存中,并不会写入实质的存储介质之中。

以上为本篇的全部内容。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,245评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,749评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,960评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,575评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,668评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,670评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,664评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,422评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,864评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,178评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,340评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,015评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,646评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,265评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,494评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,261评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,206评论 2 352

推荐阅读更多精彩内容