Java
Java IO

Java IO 21 - PushbackReader详解

简介:流是一组有顺序的,有起点和终点的字节集合。是对设备文件间数据传输的总称和抽象。

1. PushbackReader介绍

PushbackReader与前面介绍的PushbackInputStream非常相似,二者都是数据输入流;PushbackReader继承于FilterReader,用来装饰其它输出流,PushbackReader允许应用程序在读取数据的时候,将一些特定的字符推回到流中,下次读取时将会优先读取上一次被推回的字符。

PushbackReader内部维护了一个缓冲区用于装载被推回的字符,这个缓冲区的大小是可以指定的。

2. PushbackReader函数列表

  • // 包装一个输入流为回推流,并指定回推缓冲区大小
  • PushbackReader(Reader in, int size);
  • // 包装一个输入流为回推流,默认回推缓冲区大小为1
  • PushbackReader(Reader in);
  • // 读取1个字符
  • int read();
  • // 将len长度的字符读入到cbuf数组中从off开始的位置
  • int read(char cbuf[], int off, int len);
  • // 将c回推进流中,需要注意的是,
  • // 在cbuf中存放的回推数据,后回推的数据会在先回推数据的前面
  • // 也就是说,在读取buf中的数据的时候,后回推的数据会先于先回推的数据被读取
  • void unread(int c);
  • // 将cbuf中从off位置开始的长度为len的字符回推进流中
  • void unread(char cbuf[], int off, int len);
  • // 将cbuf中的字符回推进流中
  • void unread(char cbuf[]);
  • // 是否可读
  • boolean ready();
  • // 跳过n个字符
  • long skip(long n);
  • // 默认不支持mark和reset,会抛出IOException异常
  • void mark(int readAheadLimit);
  • // 默认不支持mark和reset,会抛出IOException异常
  • void reset();
  • // 默认不支持mark和reset,返回false
  • boolean markSupported();
  • // 关闭流
  • void close();

3. PushbackReader源码解析

PushbackReader的源码与PushbackInputStream非常相似,只是PushbackInputStream操作的是字节,而PushbackReader操作的是字符,下面是PushbackReader的源码,基于JDK 1.7.0_07:

  • package java.io;
  • public class PushbackReader extends FilterReader {
  • // 内部的回推缓冲区
  • private char[] buf;
  • /**
  • * pos表示下一个即将读取的字符的在回推缓冲区buf中的位置,
  • * 当buf为空,pos就是buf的长度,当buf填满时,pos为0
  • */
  • private int pos;
  • // 包装一个输入流为回推流,并指定回推缓冲区大小
  • public PushbackReader(Reader in, int size) {
  • super(in);
  • if (size <= 0) {
  • throw new IllegalArgumentException("size <= 0");
  • }
  • this.buf = new char[size];
  • this.pos = size;
  • }
  • // 包装一个输入流为回推流,默认回推缓冲区大小为1
  • public PushbackReader(Reader in) {
  • this(in, 1);
  • }
  • // 确保buf不为空,原始流没有关闭
  • private void ensureOpen() throws IOException {
  • if (buf == null)
  • throw new IOException("Stream closed");
  • }
  • // 读取1个字符
  • public int read() throws IOException {
  • synchronized (lock) {
  • ensureOpen();
  • if (pos < buf.length)
  • return buf[pos++];
  • else
  • return super.read();
  • }
  • }
  • // 将len长度的字符读入到b数组中从off开始的位置
  • public int read(char cbuf[], int off, int len) throws IOException {
  • synchronized (lock) {
  • // 确保原始流是打开的
  • ensureOpen();
  • try {
  • // 判断off和len是否合规
  • if (len <= 0) {
  • if (len < 0) {
  • throw new IndexOutOfBoundsException();
  • } else if ((off < 0) || (off > cbuf.length)) {
  • throw new IndexOutOfBoundsException();
  • }
  • return 0;
  • }
  • // 查看buf中的数据长度
  • int avail = buf.length - pos;
  • if (avail > 0) {
  • // 如果buf中有数据就先读取buf中的数据
  • if (len < avail)
  • avail = len;
  • System.arraycopy(buf, pos, cbuf, off, avail);
  • pos += avail;
  • off += avail;
  • len -= avail;
  • }
  • // 剩余不足的再从原始流中读取
  • if (len > 0) {
  • len = super.read(cbuf, off, len);
  • // 如果原始流读入为-1时,计算返回的数据
  • if (len == -1) {
  • // 如果从buf中读取的为0,则表明整个过程没有读取到数据,返回-1
  • // 如果从buf中读取到了avail字符的数据,那就返回avail即可
  • return (avail == 0) ? -1 : avail;
  • }
  • // 否则返回从buf和原始流读取的总字符数
  • return avail + len;
  • }
  • return avail;
  • } catch (ArrayIndexOutOfBoundsException e) {
  • throw new IndexOutOfBoundsException();
  • }
  • }
  • }
  • // 将c回推进流中,需要注意的是,
  • // 在buf中存放的回推数据,后回推的数据会在先回推数据的前面
  • // 也就是说,在读取buf中的数据的时候,后回推的数据会先于先回推的数据被读取
  • public void unread(int c) throws IOException {
  • synchronized (lock) {
  • ensureOpen();
  • if (pos == 0)
  • throw new IOException("Pushback buffer overflow");
  • buf[--pos] = (char) c;
  • }
  • }
  • // 将b中从off位置开始的长度为len的字符回推进流中
  • public void unread(char cbuf[], int off, int len) throws IOException {
  • synchronized (lock) {
  • ensureOpen();
  • if (len > pos)
  • throw new IOException("Pushback buffer overflow");
  • pos -= len;
  • System.arraycopy(cbuf, off, buf, pos, len);
  • }
  • }
  • // 将cbuf中的字符回推进流中
  • public void unread(char cbuf[]) throws IOException {
  • unread(cbuf, 0, cbuf.length);
  • }
  • // 是否可读
  • public boolean ready() throws IOException {
  • synchronized (lock) {
  • ensureOpen();
  • return (pos < buf.length) || super.ready();
  • }
  • }
  • // 不支持mark和reset
  • public void mark(int readAheadLimit) throws IOException {
  • throw new IOException("mark/reset not supported");
  • }
  • // 不支持mark和reset
  • public void reset() throws IOException {
  • throw new IOException("mark/reset not supported");
  • }
  • // 不支持mark和reset
  • public boolean markSupported() {
  • return false;
  • }
  • // 关闭流
  • public void close() throws IOException {
  • super.close();
  • buf = null;
  • }
  • // 跳过n个字符
  • public long skip(long n) throws IOException {
  • if (n < 0L)
  • throw new IllegalArgumentException("skip value is negative");
  • synchronized (lock) {
  • // 确保原始流是打开的
  • ensureOpen();
  • int avail = buf.length - pos;
  • // 先跳过buf缓冲区的数据
  • if (avail > 0) {
  • if (n <= avail) {
  • pos += n;
  • return n;
  • } else {
  • pos = buf.length;
  • n -= avail;
  • }
  • }
  • // 再跳过原始流中的数据
  • return avail + super.skip(n);
  • }
  • }
  • }

PushbackReader的源码与PushbackInputStream是雷同的,只是将内部维护的字节数据换成了字符数组:

  1. PushbackReader内部维护了一个缓冲区buf用于装载推回的数据,默认大小为1,可以在初始化PushbackReader的时候指定该缓冲区大小。当缓冲区已满时,如果继续推回数据会抛出提示信息为“Pushback buffer overflow”的IOException。
  2. 在每次推回数据的时候,推回的数据会在缓冲区buf中按照从后往前的顺序填充,即先推回的数据会位于缓冲区buf后面,后推回的数据会位于缓冲区buf的前面,因此在读取数据的时候,会先读取后推回的数据再读取先推回的数据。
  3. 每次读取数据,会先读取缓冲区buf中的数据(如果有),再读取原始流中的数据。
  4. 跳过数据的时候,会先跳过缓冲区buf中的数据(如果有),再跳过原始流中的数据。
  5. PushbackReader默认是不支持mark和reset操作的。

4. PushbackReader示例

下面的PushbackReader的实例改编自PushbackInputStream,功能一致,首先有一个原始输入流中有连续的a ~ z这个26个字母,在使用PushbackReader读取该流的时候,会按照我们习惯的方式分段读取,每一段进行换行并在段尾打印特殊标记[*]

  • public static void main(String[] args) throws IOException {
  • char[] serial = new char[26];
  • for (int i = 0; i < serial.length; i++) {
  • serial[i] = (char) ('a' + i);
  • }
  • System.out.println("original chars: " + new String(serial));
  • CharArrayReader charArrayReader = new CharArrayReader(serial);
  • PushbackReader pushbackReader = new PushbackReader(charArrayReader, 4);
  • System.out.println("splitted results: ");
  • int data;
  • while ((data = pushbackReader.read()) != -1) {
  • System.out.print(String.valueOf((char)data));
  • if ("gntz".indexOf(data) != -1) {
  • pushbackReader.unread('\n');
  • pushbackReader.unread(']');
  • pushbackReader.unread('*');
  • pushbackReader.unread('[');
  • } else if ("qw".indexOf(data) != -1) {
  • pushbackReader.unread(' ');
  • }
  • }
  • pushbackReader.close();
  • }

打印结果如下:

  • original chars: abcdefghijklmnopqrstuvwxyz
  • splitted results:
  • abcdefg[*]
  • hijklmn[*]
  • opq rst[*]
  • uvw xyz[*]

从源码和打印结果可以得知,在读取数据时,每遇到gntz四个字母中的一个,就会往流中回推\n]*[四个字符,再次打印时,会依次按照[*]\n的顺序进行输入,这与上面源码分析的结果一致;每遇到qw两个字母中的一个,就往流中回推一个空格字符,下次打印时就会把空格字符打印出来。