Contents
  1. 1. 使用方法
  2. 2. 成员变量
  3. 3. 构造函数
  4. 4. 读取数据
  5. 5. 推回数据

PushbackInputstream类是Java IO中的一个较特别的类,因为它可以将读出的字节再推回流中。这也就是它的名字中Pushback的由来。下面首先介绍一下它的基本用法,再分析一下它的源代码。

使用方法

首先需要构造一个PushbackInputStream对象,这个对象需要传入一个InputStream实例作为参数。之后从该InputStream绑定的数据源(例如一个文件、或者网络流)读入数据,然后将读出的数据再推回PushBackInputStream中。
如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class PushbackInputStreamTest {
public static void main(String[] args) throws Exception{
PushbackInputStream inputStream = new PushbackInputStream(
new FileInputStream("input.txt"));
int data = inputStream.read();
System.out.println(data);
inputStream.unread(data);
data = inputStream.read();
System.out.println(data);
}
}

文件中保存着一个字符串abcdefg。输出如下:

Read before push back: 97
Read after push back: 97

可见,我们读出了一个字符a(它的ascii码是97),之后又成功推回了流中。

如果我们需要一次推回多个字符,则需要在构造PushbackInputStream实例时传入一个整型参数,用于指定每次推回多最大字节数。
如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class PushbackInputStreamTest {
public static void main(String[] args) throws Exception{
PushbackInputStream input = new PushbackInputStream(
new FileInputStream("input.txt"), 10);
byte[] bytes = new byte[10];
int r = input.read(bytes);
for (byte b : bytes) {
System.out.println(b);
}
input.unread(bytes);
r = input.read(bytes);
for (byte b : bytes) {
System.out.println(b);
}
}
}

输出:
Read before push back:
97
98
99
100
101
102
103
0
0
0
Read after push back:
97
98
99
100
101
102
103
0
0
0

可见,我们可以一次读入最多10个字节(因为数据只有7个字节,所以剩余字节为0.

如果推回多字节大于构造函数中指定的最大字节数,则会发生异常:

java.io.IOException: Push back buffer is full

下面分析一下PushbackInputStream的源代码

成员变量

1
2
3
4
5
6
7
8
9
10
/**
* 缓冲区,被推回的字节将会保存在这里
*/
protected byte[] buf;
/**
* 从缓冲区读取数据时的位置,也就是读取被推回的数据时的下标。当缓冲区为空时,pos为buf.length(即没有推回数据,故不能从缓冲区读取);当缓冲区所有字节均有数据时,则pos为0
*
*/
protected int pos;

构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 通过指定size的缓冲数组以及一个InputStream实例,来构造一个PushbackInputStream对象
*/
public PushbackInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("size <= 0");
}
this.buf = new byte[size];
this.pos = size;
}
/**
* 若不指定缓冲区大小,则默认为1,即每次只能推回一个字节
*/
public PushbackInputStream(InputStream in) {
this(in, 1);
}

读取数据

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* 读取数据,若缓冲区有数据,则从缓冲区的pos位置开始读取;若缓冲区无数据,则调用超类的read方法读取数据
*/
public int read() throws IOException {
// 确保流没有关闭
ensureOpen();
if (pos < buf.length) {
return buf[pos++] & 0xff;
}
return super.read();
}
/**
* 将数据读入一个byte数组中,参数off是目标数组中的起始偏移量,参数len是读入数据的最大字节数
*/
public int read(byte[] b, int off, int len) throws IOException {
ensureOpen();
// 校验参数是否合法,b不能为null,off不能为负,且读入的最大字节数不能超过数组所能接受的最大字节数。
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;
}
// 若要读取的数据数超过了缓冲区的数据数,则读完被推回的数据后,继续从包裹的InputStream中读取数据
if (len > 0) {
len = super.read(b, off, len);
if (len == -1) {
// 缓冲区无数据,包裹的InputStream也没有数据
return avail == 0 ? -1 : avail;
}
return avail + len;
}
return avail;
}

推回数据

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
/**
* 推回一个字节数据,即将它拷贝到本类的缓冲数组中
*/
public void unread(int b) throws IOException {
ensureOpen();
// pos为0说明缓冲数组已满
if (pos == 0) {
throw new IOException("Push back buffer is full");
}
// 从后往前赋值
buf[--pos] = (byte)b;
}
/**
* 推回字节数组的一部分到缓冲数组中,
*/
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);
}

以上就是这个类的主要方法。我们可以看出,当单个推回数据时,是逆序插入缓冲数组,同时读取数据时,是正序读出。所以是后入先出。

Contents
  1. 1. 使用方法
  2. 2. 成员变量
  3. 3. 构造函数
  4. 4. 读取数据
  5. 5. 推回数据