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);
}

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

ArrayList有三个构造器,分别是无参构造器,传入一个整形参数的构造器,以及传入一个Collection实例的构造器。本文主要介绍前两种构造器。

无参构造器

示例代码:

1
2
3
4
5
6
7
8
9
public class ArrayListTest {
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
strings.add("abc");
}
}

下面看调用构造器以及添加元素的代码

1
2
3
4
5
6
7
/**
* 用初始的10的容量大小构造一个空的list
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

从上述构造器可以看出,无参构造器只是很简单将常量DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋给成员变量elementData。这个成员变量和常量如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access

从注释可以看出,DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空的数组实例,用来作为默认大小的空List。它与EMPTY_ELEMENTDATA同时存在,是为了当第一个元素被添加到list中时,能算出要扩展多大的容量。

elementData是ArrayList中用于实际存储元素的数组。一个ArrayList实例的容量就是这个数组的长度。任何调用了默认构造器生成的ArrayList实例,都执行了elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA,当第一个元素被添加到list中时,都会被扩展到默认的容量大小,即DEFAULT_CAPACITY(10)。

末尾添加元素的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
// 扩容之后将新增的元素加到末尾,同时将list的size加一。
elementData[size++] = e;
return true;
}

该方法首先调用了ensureCapacityInternal函数,用于确保容量足以添加一个元素。ensureCapacityInternal方法:

1
2
3
4
5
6
7
8
9
private void ensureCapacityInternal(int minCapacity) { // 插入“abc”时,minCapacity为1
// 当调用无参构造器时,初始存储元素的数组是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 比较参数和默认容量大小,取大者作为扩展多容量
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // minCapacity变为10
}
ensureExplicitCapacity(minCapacity);
}

之后调用了ensureExplicitCapacity函数,继续扩展容量大操作:

1
2
3
4
5
6
7
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 检查目标容量是否大于当前数组的实际长度
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

上述方法继续调用grow函数,完成扩容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 容量扩充增量为当前数组容量大50%
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 若扩充后仍达不到目标容量,则直接使用目标容量作为新容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 若超过最大容量,则调用hugeCapacity函数
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 调用工具函数扩容,末尾用null补全
elementData = Arrays.copyOf(elementData, newCapacity);
}

从注释可知,该方法增长容量以确保list至少能保存传入参数大小数量多元素。之后,就可以插入元素“abc”。插入之后,list的size为1,容量为10.

Object类是Java中的一个全局超类——每一个Java类都扩展了它。但是你不必显示地如下声明:

1
public class Employee extends Object

equals方法

引用同一

equals方法是Object类中用于判断一个对象和另一个对象是否相等的方法。在Object类中的实现如下:

1
2
3
public boolean equals(Object obj) {
return (this == obj);
}

从源代码可知,该实现是检查两个对象的引用是否相等,也即是检查两个对象是否指向Java堆中的同一个对象。这是因为,如果两个对象的引用指向同一个Java堆中的对象,则这两个对象必然是相等的。对极少数类的
对象来说,这个实现已经足够了。例如两个PrintStream对象。

状态相等

如上文所述,对大多数类来说,Object中的equals实现是不够用的。因为很多时候,我们认为两个对象相等是基于它们具有相同的状态(即对象的属性,field)。例如,两个Employee对象,如果它们具有相同的姓名(name),
工资(salary)和雇佣日期(hire day),那它们就是相等的。它的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Employee {
...
public boolean equals(Object otherObject) {
if (this == otherObject)
return true;
if (otherObject == null)
return false;
if (getClass() != otherObject.getClass())
return false;
Employee other = (Employee) otherObject;
return name.equals(other.name)
&& salary == other.salary;
&& hireDay.equals(other.hireDay);
}
}

上面的代码中,首先判断两个对象是否同一,其次判断被比较对象是否为null,再次比较两个对象所属的类是否相同(在此处,假定必须类相同才有可能相等),最后比较各个状态是否相等。注意如果状态也是对象的话,
则会递归调用它们的equals方法。

这里要注意的是,Employee对象的name和hireDay等状态是有可能为null的,为了确保在这种情况下程序也是正确的,可以使用Objects.equals方法。调用Objects.equals(a, b),若两个参数都是null返回true,
若其中一个为null返回false,以上都不成立则调用a.equals(b)。因此,上面方法的最后一条语句可修改为

1
2
3
return Objects.equals(name, other.name)
&& salary == other.salary;
&& Objects.equals(hireDay, other.hireDay);

当在扩展类中定义了equals方法时,须首先调用超类的equals方法,如果返回false,则两个对象不可能相等。若返回true,即是超类定义的状态相等。则继续比较子类定义的状态。