BufferedInputStream 使用 装饰者模式 对InputStream的功能进行了加强。通过阅读该类的代码实现,对理解装饰者模式大有助益。
FilterInputStream 类的定义就已经用到了装饰者模式——它本身继承自抽象类InpuStream,又定义了一个InputStream的成员变量 in,而且对继承的所有抽象方法的实现都是简单的通过成员变量in来实现的,比如 close() 方法在FilterInputStream中的实现:
FilterInputStream类其实就是一个 Wrapper 类,这样做的好处是,所有想对InputStream类提供的功能进行增强的类,只需继承FilterInputStream类实现需要增强的方法即可,而无须体验直接继承InputStream类要实现其定义的所有抽象方法带来的枯燥冗余感。
正如其名所述,它其实就是对InputStream提供了一个缓冲区。该缓冲区的存在,减少了应用程序访问磁盘的次数,从而提高了程序的性能。那它是怎么实现的呢?
public synchronized int read() throws IOException {
if (pos >= count) {
// 缓冲区的数据已经使用完
// 填充缓冲区
fill();
if (pos >= count) // 缓冲区依然没有内容,说明数据已经读完
return -1;
}
return getBufIfOpen()[pos++] & 0xff;
}
结合代码中的注释,我们可以得出read()方法的作用是:在确保缓冲区有数据时,从缓冲区中读出一个字节。
public synchronized void mark(int readlimit) {
marklimit = readlimit;
// 将当前缓存区的起始位置保存起来
markpos = pos;
}
该方法的作用在于记录下次 重读 的起始位置
private void fill() throws IOException {
// 需要注意该方法只会在缓冲区用完后才会被调用,所以其实有些情况根本不用考虑,比如 pos < buffer.length
// 确保字节流没有关闭
byte[] buffer = getBufIfOpen();
if (markpos < 0) /* 没有调用mark()方法 */
pos = 0; /* no mark: throw away the buffer */
else if (pos >= buffer.length) /* no room left in buffer */
if (markpos > 0) { /* can throw away early part of the buffer */
// 调用过 mark() 方法
int sz = pos - markpos;
// 将markpos及以后的所有数据移动到缓冲区的最前方(这时pos已指向了缓冲区的最末尾处)
System.arraycopy(buffer, markpos, buffer, 0, sz);
pos = sz;
markpos = 0;
} else if (buffer.length >= marklimit) {
markpos = -1; /* buffer got too big, invalidate mark */
pos = 0; /* drop buffer contents */
} else if (buffer.length >= MAX_BUFFER_SIZE) {
throw new OutOfMemoryError("Required array size too large");
} else { /* grow buffer */
// 这里 markpos可能为0,为了保证允许从缓冲区开头重读,所以需要对缓冲区扩容,将新读取的数据放到扩容缓冲区中
int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
pos * 2 : MAX_BUFFER_SIZE;
if (nsz > marklimit)
nsz = marklimit;
byte nbuf[] = new byte[nsz];
System.arraycopy(buffer, 0, nbuf, 0, pos);
if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
// Can't replace buf if there was an async close.
// Note: This would need to be changed if fill()
// is ever made accessible to multiple threads.
// But for now, the only way CAS can fail is via close.
// assert buf == null;
throw new IOException("Stream closed");
}
buffer = nbuf;
}
count = pos;
// 这里就是对 read 方法进行增强的体现——通过调用底层依赖的InputStream对象的 read 方法来填充缓冲区
// 将(因移动)而空出来的后部分缓冲区填满
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
if (n > 0)
count = n + pos;
}
结合注释,可以看出 fill() 方法的目的在于在保证允许对缓冲区重读的前提下,尽可能地将缓冲区 buf 填满。
private int read1(byte[] b, int off, int len) throws IOException {
int avail = count - pos;
if (avail <= 0) {
//缓冲区已经没有数据了,尝试填充缓冲区
/* If the requested length is at least as large as the buffer, and
if there is no mark/reset activity, do not bother to copy the
bytes into the local buffer. In this way buffered streams will
cascade harmlessly. */
if (len >= getBufIfOpen().length && markpos < 0) {
return getInIfOpen().read(b, off, len);
}
fill();
avail = count - pos;
if (avail <= 0) return -1;
}
int cnt = (avail < len) ? avail : len;
// 直接从缓冲区读取数据
System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
pos += cnt;
return cnt;
}
public synchronized int read(byte b[], int off, int len)
throws IOException {
getBufIfOpen(); // Check for closed stream
// 通过位或运算,如果其中的一项出现零,则最终的结果为0。off + len 的出现是为了防止 off与len都为正的情况下,off+len溢出为负的情况
if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
// 循环从缓冲区中读取数据
int n = 0;
for (;;) {
int nread = read1(b, off + n, len - n);
if (nread <= 0)
return (n == 0) ? nread : n;
n += nread;
if (n >= len)
return n;
// if not closed but no bytes available, return
InputStream input = in;
if (input != null && input.available() <= 0)
return n;
}
}
BufferedInputStream 的所有读操作都是基于底层的 字节数组缓冲区buf 进行的,其使用装饰者模式进行功能增强的代码主要体现在 fill() 方法中的 getInIfOpen().read(buffer, pos, buffer.length - pos); 。除了装饰者模式的运用外,其实还有几点值得我们学习: