另外要说明一下 mark(int readlimit) 参数,readlimit 是告诉系统,过了这个 mark 点之后,给本宫记住往后的 readlimit 个字节,因为到时候 reset 之后,要从 mark 点开始读取的;但实际情况和 jdk 文档有出入,很多情况下调用 mark(int readlimit) 方法后,即使读取超过 readlimit 字节的数据,mark 标记仍有效,这又是为什么呢?网上有人解答,但我还是决定亲自探索一番。
我们这个实例引用的实际对象是 ByteArrayInputStream 先看一下它的源码:
/* Note: The readAheadLimit for this class has no meaning.*/ public void mark(int readAheadLimit) { mark = pos; }好家伙,它说这个参数对于这个类没有任何作用。
注意:这段是源码分析可看可不看,跳过不影响阅读那我们在看看其他的 InputStream 子类,经验证,FileInputStream 和一些实现类不支持 mark() 方法,我们看看 BufferedInputStream类源码:
我先把一些字段的含义说明一下:
count 索引1大于缓冲区中最后一个有效字节的索引。 该值始终在0到buf.length的范围内; 元素buf [0]到buf [count-1]包含从底层输入流获得的缓冲输入数据。在 read() 方法中读完数据返回 -1 就是因为if (pos >= count) return -1;
pos 指缓冲区中的当前位置。 这是要从 buf 数组中读取的下一个字符的索引。
该值始终在 0 到 count 范围内。 如果它小于 count,则 buf [pos] 是要作为输入提供的下一个字节; 如果它等于 count ,则下一个读取或跳过操作将需要从包含的输入流中读取更多字节。(这句话不理解没关系)
markpos是调用最后一个 mark() 方法时 pos 字段的值。该值始终在-1到pos的范围内。 如果输入流中没有标记位置,则此字段为-1。
BufferedInputStream 是每次读取一定量的数据到 buf 数组中的,设置了 readlimit 肯定是想让数组从 mark 索引开始至少记录 mark + readlimit 索引。
public synchronized void mark(int readlimit) { marklimit = readlimit; markpos = pos; } public synchronized void reset() throws IOException { getBufIfOpen(); // Cause exception if closed if (markpos < 0) throw new IOException("Resetting to invalid mark"); pos = markpos; } private void fill() throws IOException { byte[] buffer = getBufIfOpen(); if (markpos < 0) pos = 0; /* 没有标记就直接丢掉缓存,使用buffer取新数据 */ else if (pos >= buffer.length) /* 缓冲区中当前位置比buffer数组大,才执行下面代码 */ if (markpos > 0) { /* 可以把 markpos 左边的数据丢掉 */ int sz = pos - markpos; // 需要缓存的字节长度,从 markpos 开始 System.arraycopy(buffer, markpos, buffer, 0, sz); // 复用内存空间 pos = sz; markpos = 0; } else if (buffer.length >= marklimit) { // 如果 buffer 的长度已经大于 marklimit markpos = -1; /* 那 mark 就失效了*/ pos = 0; /* 删除buffer内容,取新数据 */ } else if (buffer.length >= MAX_BUFFER_SIZE) { // 如果buffer过长就抛错 throw new OutOfMemoryError("Required array size too large"); } else { /* buffer 还没 marklimit 大,扩容到 pos 的2倍或者最大值 */ 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)) { throw new IOException("Stream closed"); } buffer = nbuf; } count = pos; int n = getInIfOpen().read(buffer, pos, buffer.length - pos); if (n > 0) count = n + pos; }可以得出:设置标记后,
如果缓冲区中当前位置比 buffer 数组小,也就是还没读完 buffer 数组,那 mark 标记不会失效;
下次继续读取,超过 buffer 大小字节后,判断 markpos 是否大于0,如果 markpos 大于0,即还在 buffer 数组内,则把 markpos 左边的数据清除,markpos 指向 0 , 复用内存空间,并设置 buffer 的大小为 (pos - markpos) 的值;
再继续读取,此时 markpos 肯定不在 buffer 数组包含范围了,此时判断 buffer 的长度是否大于等于
marklimit ,如果小于 marklimit ,那说明设置 mark 后读取的数据长度还没达到要求的 marklimit 了,给我继续,保持从 mark 点开始缓存, mark 标记不会失效。然后 buffer 就扩容到Math.min(2倍 pos 或最大值 ,marklimit);
再继续读取,同上,buffer 这么努力扩容,总有大于 marklimit 的时候,这时说明设置 mark 后继续读取的数据长度已经超过要求的 marklimit 了,仁尽义至,标记失效;
我们就只分析了 ByteArrayInputStream 和 BufferedInputSteam 类的算法,其它输入流不知道。因此 mark() 方法标记时,务必考虑好 readlimit 的值。
OutputStreamOutputStream通常始终连接到某个数据目标,如文件,网络连接,管道等。 OutputStream的数据目标是写入OutputStream的所有数据最终结束的地方。
write(byte)