Java I/O 16 - StringReader & StringWriter

  关于 java.io.StringReader java.io.StringWriter 的部分笔记,这两个类维护了一套对于String/ StringBuffer的流操作集合。其底层的流内容来源和最终存储位置也是String/StringBuffer而非其他第三方的输入输出流位置。本文演示代码段的执行环境基于JDK版本1.7

概述

  StringReader和StringWriter的底层输入输出位置是String/StringBuffer,事实上是把String/StringBuffer当做了一个虚拟的输入输出流来对待处理而不依赖第三个真正意义上的输入输出流。所有的操作都是围绕着String/ StringBuffer进行。这两个类可以用在一些需要字符流作为参数,且内容单纯的只有String的场景中。

继承关系

1
2
3
4
5
6
7
8
9
// StringReader
--java.lang.Object
--java.io.Reader
--java.io.StringReader

// StringWriter
--java.lang.Object
--java.io.Writer
--java.io.StringWriter

实现接口

类名 实现接口
StringReader Closeable, AutoCloseable,Readable
StringWriter Closeable, Flushable, AutoCloseable, Appendable

StringReader

Constructor Summary

public StringReader(String s)

1
2
3
4
public StringReader(String s) {
this.str = s;
this.length = s.length();
}

  初始化一个String内容的输入流,流的源头是入参字符串s。输入流的内容的长度是s的字符串长度。

部分方法

public int read()

1
2
3
4
5
6
7
8
public int read() throws IOException {
synchronized (lock) {
ensureOpen();
if (next >= length)
return -1;
return str.charAt(next++);
}
}

  读取一个字符的内容并返回。通过synchronized关键字保证了多线程环境下的线程安全。如果底层的字符串不为空,那么返回下一个字符串。如果已经到达了字符串尾部,那么就返回-1标识文件已经读取完毕。第3行代码中的ensureOpen()方法则用来判断底层的字符串是否有效。

1
2
3
4
private void ensureOpen() throws IOException {
if (str == null)
throw new IOException("Stream closed");
}

  如果字符为空,那么向上抛出一个异常提示当前流已关闭。

public int read(char cbuf[], int off, int len)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}
}

  从底层读取长度为len的字符内容并保存到字符数组cbuf中。通过synchronized关键字保证了多线程环境下的线程安全。第3行代码用来判断底层字符串内容是否存在且可用。第4 ~ 6行代码用来完成参数的边界校验,防止越界溢出。第10 ~ 11行代码如果已经读到了字符串尾部,那么返回-1。第12行代码用来判断当前剩余未读取的字符串长度和入参长度len之间的大小关系,如果剩余未读取的字符串长度小于len,那么剩余未读取的字符串会全部返回,否则就返回长度为len的字符串内容。第13行代码完成实际的读取操作。之后计算实际读取的字符串长度并返回。

public long skip(long ns)

1
2
3
4
5
6
7
8
9
10
11
12
public long skip(long ns) throws IOException {
synchronized (lock) {
ensureOpen();
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;
}
}

  跳过流底层字符串中指定长度的字符数。下一个读取的位置会是当前next+ns位置。如果ns为负数,那么执行的实际效果是会前推到next-ns位置。通过synchronized关键字保证了多线程环境下的线程安全。第3行代码用来判断底层字符串内容是否存在且可用。第4 ~ 5行代码如果当前字符串已经到达字符串尾部(正常读取或者跳过)那么直接返回0。第7 ~ 9行代码计算实际需要跳过的字符长度,具体如下分析:

  1. ns > 0。第7行代码会返回两个计算值的较小值作为实际跳过长度值。第8行代码由于next永远为正,所以得到的较大值是第7行代码的计算结果。第9行代码执行正数加法,计算后的next值大于计算前的next值,下一个读取位置后移:
  2. ns < 0,|ns| < next。第7行代码永远返回负数值ns。第8行代码由于 |ns| < next,所以ns > -next,所以实际返回ns。第9行代码执行负数加法,计算后的next小于计算前的next值但next依然大于0。下一个读取位置前移;
  3. ns < 0,|ns| > next。第7行代码永远返回负数值ns。第8行代码由于 |ns| > next,所以ns < -next,所以实际返回 -next。第9行代码执行负数加法,计算后的next值等于0。下一个读取位置归零到字符串第一个位置。

最后返回实际跳过的位置,综上分析,该返回值可能为负数。

public boolean ready()

1
2
3
4
5
6
public boolean ready() throws IOException {
synchronized (lock) {
ensureOpen();
return true;
}
}

  通知方法调用方当前输入流可以对外提供数据读取操作。如果ensureOpen()没有抛出异常,那么底层字符串不为空,那么返回true表示有内容可供读取。

public boolean markSupported()

1
2
3
public boolean markSupported() {
return true;
}

  是否支持标记重读。StringReader支持标记重读,所以返回true。

public void mark(int readAheadLimit)

1
2
3
4
5
6
7
8
9
public void mark(int readAheadLimit) throws IOException {
if (readAheadLimit < 0){
throw new IllegalArgumentException("Read-ahead limit < 0");
}
synchronized (lock) {
ensureOpen();
mark = next;
}
}

  记录当前读取位置。该位置值会在下次调用reset()方法时重新赋值给next完成重读操作。在StringReader中流数据来自于String字符串,所以没有实际的关于mark方法调用之后可读的字符长度的限制,所以入参readAheadLimit必须大于0。

public void reset()

1
2
3
4
5
6
public void reset() throws IOException {
synchronized (lock) {
ensureOpen();
next = mark;
}
}

  重置下一个读取位置。如果在调用该方法之前没有调用过mark()方法,mark一直保持初始值(即mark = 0)状态,那么该方法调用后下一个读取位置会回归到字符串首部。

public void close()

1
2
3
public void close() {
str = null;
}

  关闭流并释放占用资源。在StringReader中直接将底层字符串置为null。

StringWriter

Constructor Summary

public StringWriter()

1
2
3
4
public StringWriter() {
buf = new StringBuffer();
lock = buf;
}

  初始化一个String输出流。底层输出位置是一个StringBuffer,写入的数据会存储到StringBuffer中。之所以使用StringBuffer是因为StringWriter可能用于多线程环境,而StringBuffer可以保证多线程环境下的线程安全。

public StringWriter(int initialSize)

1
2
3
4
5
6
7
public StringWriter(int initialSize) {
if (initialSize < 0) {
throw new IllegalArgumentException("Negative buffer size");
}
buf = new StringBuffer(initialSize);
lock = buf;
}

  初始化一个String输出流。底层输出位置是一个StringBuffer,写入的数据会存储到StringBuffer中。通过参数initialSize来指定StringBuffer的长度。

部分方法

public void write(int c)

1
2
3
public void write(int c) {
buf.append((char) c);
}

  向缓冲区buf中写入一个字符数据。

public void write(char cbuf[], int off, int len)

1
2
3
4
5
6
7
8
9
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);
}

  向缓冲区buf中写入数组cbuf中的字符数据。第2 ~ 4行代码完成入参的有效性校验,避免发生越界溢出。第8行代码则将cbuf中的内容追加到buf中。

public void write(String str)

1
2
3
public void write(String str) {
buf.append(str);
}

  将一个String字符串写入到缓冲区buf中。

public void write(String str, int off, int len)

1
2
3
public void write(String str, int off, int len)  {
buf.append(str.substring(off, off + len));
}

  将一个String字符串写入到缓冲区buf中,由参数off指定写入的起始位置,由参数len指定实际写入的字符长度。

public StringWriter append(CharSequence csq)

1
2
3
4
5
6
7
public StringWriter append(CharSequence csq) {
if (csq == null)
write("null");
else
write(csq.toString());
return this;
}

  将一个CharSequence对象中包含的内容写入到底层的缓冲区buf中。该方法的实际效果等同于write(String str)

public StringWriter append(CharSequence csq, int start, int end)

1
2
3
4
5
public StringWriter append(CharSequence csq, int start, int end) {
CharSequence cs = (csq == null ? "null" : csq);
write(cs.subSequence(start, end).toString());
return this;
}

  将一个CharSequence对象中从起始位置start到结束end之间的内容写入到底层输出流的缓冲区buf中。

public StringWriter append(char c)

1
2
3
4
public StringWriter append(char c) {
write(c);
return this;
}

  将一个字符写入到底层输出了的缓冲区buf中。该方法的实际效果等同于write(int c)

public String toString()

1
2
3
public String toString() {
return buf.toString();
}

  将底层StringBuffer中的内容转换成String字符串并输出给方法调用方。

public StringBuffer getBuffer()

1
2
3
public StringBuffer getBuffer() {
return buf;
}

  返回底层StringBuffer自身给方法调用方。

public void flush()

1
2
public void flush() {
}

  将buf缓冲区中保存的数据推到底层输出流中。该方法的方法体中不含有任何代码段,因为底层输出位置就是一个StringBuffer,所以无需做任何操作。

public void close() throws IOException

1
2
public void close() throws IOException {
}

  关闭当前输出流。该方法的方法体中不含有任何操作,同时该方法可以多次重复调用而不会抛出异常。

涉及基础知识点

  1. NIL

参考文献

  1. NIL




------------- End of this article, thanks! -------------


  版权声明:本文由Nathan R. Lee创作和发表,采用署名(BY)-非商业性使用(NC)-相同方式共享(SA)国际许可协议进行许可,转载请注明作者及出处。
  本文作者为 Nathan R. Lee
  本文标题为 Java I/O 16 - StringReader & StringWriter
  本文链接为 https://marcuseddie.github.io/2018/java-StringReader-StringWriter.html