Java I/O 07 - PushbackInputStream

  关于 java.io.PushbackInputStream 的部分笔记,这个类可以完成对输入流数据的回退操作。也就是说,通过read方法从底层输入流中读出的数据可以在被退回到输入流中,这样下次read方法将会首先读取到被退回到输入流中的数据。本文演示代码段的执行环境基于JDK版本1.7

概述

  PushbackInputStream提供了unread方法来将已经从底层输入流中读出的字节数据再回退到输入流中,实现回退(push back)或者再读(re-read)操作。在调用unread方法后再次调用read方法时,获取的将会是被回退的数据,直到回退的数据被读取完毕,才会获取到新的输入流数据。

继承关系

1
2
3
4
5
// PushbackInputStream
--java.lang.Object
--java.io.InputStream
--java.io.FilterInputStream
--java.io.PushbackInputStream

实现接口

类名 实现接口
PushbackInputStream Closeable,, AutoCloseable

部分方法

public int read()

1
2
3
4
5
6
7
public int read() throws IOException {
ensureOpen();
if (pos < buf.length) {
return buf[pos++] & 0xff;
}
return super.read();
}

  读取一个字节数据并返回。因为在设计时,如果缓冲区数组buf是空的,那么pos的值等于buf的长度,所以如果第3行的判断满足,那么直接返回缓冲区数组buf中的下一个字节,此时缓冲区中尚有待读取的数据。否则,缓冲区中的数据已经全被读完,那么直接调用底层输入流的read方法返回一个字节数据。

public int read(byte[] b, int off, int len)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public int read(byte[] b, int off, int len) throws IOException {
ensureOpen();
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}

int avail = buf.length - pos;
if (avail > 0) {
if (len < avail) {
avail = len;
}
System.arraycopy(buf, pos, b, off, avail);
pos += avail;
off += avail;
len -= avail;
}
if (len > 0) {
len = super.read(b, off, len);
if (len == -1) {
return avail == 0 ? -1 : avail;
}
return avail + len;
}
return avail;
}

  读取数据存储到入参b中。第2行代码用于保证底层输入流可用。第3 ~ 9行代码用于判断各种边界条件和参数校验。第11行代码用来计算缓冲区数组是否还有未读取的数据,如果有,那么第12 ~ 20行代码将首先从缓冲区数组中读取数据。第13 ~ 15行的代码用来判断缓冲区数组的未读取数据量是否满足入参len的需求,如果满足,那么b中的数据将全部来源于缓冲区数组,否则b的数据将从缓冲区数组和底层输入流共同获得。第21 ~ 27行代码会从底层输入流中获取数据。如果未从底层输入流中获得任何数据(即返回-1),那么就根据是否从缓冲区数组中获得了一个及以上字节的数据来判断最终需要返回的已读取字节数。

public void unread(int b)

1
2
3
4
5
6
7
public void unread(int b) throws IOException {
ensureOpen();
if (pos == 0) {
throw new IOException("Push back buffer is full");
}
buf[--pos] = (byte)b;
}

  将已经读取出来的一个字节再退回到输入流中。这里会将入参数据b存储到缓冲区数组buf中,因为PushbackInputStream的read方法都是首先从缓冲区数组中获取数据,所以将b存储到缓冲区数组后,下一次调用read方法将会首先读取到先前存储到缓冲区数组中的数据b。

public void unread(byte[] b, int off, int len)

1
2
3
4
5
6
7
8
public void unread(byte[] b, int off, int len) throws IOException {
ensureOpen();
if (len > pos) {
throw new IOException("Push back buffer is full");
}
pos -= len;
System.arraycopy(b, off, buf, pos, len);
}

  将数组b的数据回退到输入流中。基本思路和unread(int b)相同,不同的是这里调用了System.arraycopy()方法来完成数组与数组之间的数据复制。

public void unread(byte[] b)

1
2
3
public void unread(byte[] b) throws IOException {
unread(b, 0, b.length);
}

 将数组b的数据回退到输入流中。

public int available()

1
2
3
4
5
6
7
8
public int available() throws IOException {
ensureOpen();
int n = buf.length - pos;
int avail = super.available();
return n > (Integer.MAX_VALUE - avail)
? Integer.MAX_VALUE
: n + avail;
}

  返回剩余可读取的字节数。这个结果涵盖了缓冲区数组中的剩余可读取的字节数和底层输入流中剩余刻度的字节数。第5行代码可以理解成如下代码:

1
2
3
return (n + avail)> Integer.MAX_VALUE
? Integer.MAX_VALUE
: n + avail;

public long skip(long n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public long skip(long n) throws IOException {
ensureOpen();
if (n <= 0) {
return 0;
}

long pskip = buf.length - pos;
if (pskip > 0) {
if (n < pskip) {
pskip = n;
}
pos += pskip;
n -= pskip;
}
if (n > 0) {
pskip += super.skip(n);
}
return pskip;
}

  跳过输入流的指定长度的数据内容。在具体执行时,会首先判断缓冲区数组中是否有待读取的数据,如果有,那么会首先跳过缓冲区数组中的内容,第8 ~ 14行代码执行的便是跳过缓冲区数组内容的操作。如果缓冲区数组的内容都已经被跳过,此时缓冲区数组中已经没有待读取的数据了,而实际跳过字节数尚未达到入参n的要求,那么会直接跳过底层输入流中的数据。第15 ~17行代码则是对底层输入流直接处理。最后返回实际跳过的字节数。需要注意的是,在一些比较特殊的场景中,比如在读到文件结束时,整个输入流中已经没有可供跳过的内容了,从而导致实际跳过的字节数可能小于入参n。

public synchronized void close()

1
2
3
4
5
6
7
public synchronized void close() throws IOException {
if (in == null)
return;
in.close();
in = null;
buf = null;
}

  在结束调用后需要释放底层输入流的占用,同时释放缓冲区数组。

其他方法

1
2
3
4
5
6
7
8
9
10
11
12
13
// PushbackInputStream不支持标记下一个读取位置
public boolean markSupported() {
return false;
}

// PushbackInputStream的mark方法不做任何事情
public synchronized void mark(int readlimit) {
}

// PushbackInputStream的reset方法不做任何事情,仅发出异常提示信息
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}

涉及基础知识点

  1. NIL

参考文献

  1. LIUXUN1993728. Java IO操作——回退流PushbackInputStream [E]



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


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