InputStream的结构图如下(其中StringBufferInputStream 和 LineNumberInputStream 都已经过时):
每个实现可以针对不同的输入源,FileInputStream针对文件,ByteArrayInputStream针对内存byte数组等,
其中FilterInputStream是一个特殊的实现,应用了装饰者模式,其子类可以用来装饰其他实现类
先看看ByteArrayInputStream的源码: ByteArrayInputStream中有个byte数组,用来存储需要读取的数据, 读取时直接根据数组下标来获取对应的数据
还定义了2个int类型的变量pos和mark,其中,pos是用来标识下一个读取的数据在数组中的下标,每次读取后pos下标就增加相应的长度,指向下一次读取的下标
而mark则用来指向某个特定下标,初始化时一般指向第一个元素的下标,可以在读取到特定位置时使用mark=pos来定位到其他下标,也可以使用pos=mark来重新读取前面的数据
//内部数组用来保存初始化时传入的数组
protected byte buf[];
//下一个读取的数组下标
protected int pos;
//位置标记,默认为0,也可以使用mark()方法更改,用来标记到特定位置,使得读取到其他位置后可以用 reset()重置到mark标记的地方,重新开始读取
protected int mark = 0;
protected int count;
//初始化时,直接将内部数组指向传入的数组,pos指向数组第一个元素
public ByteArrayInputStream(byte buf[]) {
this.buf = buf;
this.pos = 0;
this.count = buf.length;
}
//如果初始化时,希望从指定下标开始读取,只需要将pos更改为这个下标即可
public ByteArrayInputStream(byte buf[], int offset, int length) {
this.buf = buf;
this.pos = offset;
this.count = Math.min(offset + length, buf.length);
this.mark = offset;
}
//read则直接根据下标返回数组元素(先判断是否越界)
public synchronized int read() {
return (pos < count) ? (buf[pos++] & 0xff) : -1;
}
//读取多个到指定数组
public synchronized int read(byte b[], int off, int len) {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
}
if (pos >= count) {
return -1;
}
int avail = count - pos;
if (len > avail) {
len = avail;
}
if (len <= 0) {
return 0;
}
System.arraycopy(buf, pos, b, off, len);
pos += len;
return len;
}
//可以跳过指定长度的下标进行读取,实际只要将pos加上相应长度即可(在不越界的情况下)
public synchronized long skip(long n) {
long k = count - pos;
if (n < k) {
k = n < 0 ? 0 : n;
}
pos += k;
return k;
}
public synchronized int available() {
return count - pos;
}
public boolean markSupported() {
return true;
}
//mark和pos的相互切换,使得读取过程中,可以返回到某些指定地方开始重新读取
public void mark(int readAheadLimit) {
mark = pos;
}
public synchronized void reset() {
pos = mark;
}
再看看BufferedInputStream, 源码看起来跟ByteArrayInputStream类似,这里使用装饰者模式, 构造函数中需要有一个inputstream,可以用来对fileinputstream等进行包装
跟ByteArrayInputStream不同的是这里有一个默认的缓存区,也可以自己定义大小,读取的时候先从缓存区读取数据,如果缓存区没有数据了,那么需要从输入源读取数据到缓存区
好处在于不用一次性把数据全部加载到内存,也不用频繁去请求输入源的数据, 具体体现在fill()方法上面,在read的时候会先判断缓冲区是否还有数据可读,没有就fill()
public synchronized int read() throws IOException {
if (pos >= count) {//无数据读取了,需要从缓冲区读取
fill();
if (pos >= count)
return -1;
}
return getBufIfOpen()[pos++] & 0xff;
}
private void fill() throws IOException {
byte[] buffer = getBufIfOpen();
if (markpos < 0)
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 */
int sz = pos - markpos;
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 */
int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
pos * 2 : MAX_BUFFER_SIZE;//长度为原来数组的2倍
if (nsz > marklimit)
nsz = marklimit;
byte nbuf[] = new byte[nsz];
System.arraycopy(buffer, 0, nbuf, 0, pos);//先将原数组copy到新的数组
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;
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);//读取输入源的数据,填充到新数组中,作为缓存
if (n > 0)
count = n + pos;
}