InputStream、FilterInputStream类中并没有实现mark、markSupported和reset这三个方法。所以这两个类无法实现流的复用,即:调用reset方法重复读取已经消费过的流数据。可以通过BufferedInputStream和ByteArrayInputStream两个类来实现流的复用。

1、mark、markSupported和reset含义:

  • java.io.InputStream.markSupported() :该输入流是否支持mark()和reset()方法
  • java.io.InputStream.mark(int readlimit) :标志输入流的当前位置,随后调用reset()方法将该流重新定位到最近标记的位置;参数readlimit表示:在标记位置失效前可以读取字节的最大限制
  • java.ip.InputStream.reset() throws IOException:将此流重新定位到最后一次对此输入流调用 mark 方法时的位置。
  • markSupported 返回 true: 如果创建流以后未调用方法 mark,或最后调用 mark 以后从该流读取的字节数大于最后调用 mark 时的参数,则可能抛出 IOException。 如未抛IOException,则将该流重新设置为最近一次调用 mark 以后的位置,从而实现重复消费。
  • markSupported 返回 false:对 reset 的调用可能抛出 IOException,或者reset 不执行任何操作(例如InputStream类)

2、BufferedInputStream类调用mark和reset:

BufferedInputStream重写了父类FilterInputStream的mark和resetf方法,其有支持 mark 和 reset 方法的能力。

BufferedInputStream中,定义了一个byte数组来存放流,文件等数据(俗称缓冲区数组),在构造函数中可以指定缓冲区大小(默认8192)

public class BufferedInputStream extends FilterInputStream {

private static int DEFAULT_BUFFER_SIZE = 8192;
private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
protected volatile byte buf[];
protected int count;
protected int pos;
protected int markpos = -1;
protected int marklimit;

public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}

public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}

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

public boolean markSupported() {
return true;
}
}

根据JAVA官方文档的描述,mark(int readlimit)方法表示,标记当前位置,并保证在mark以后最多可以读取readlimit字节数据,mark标记仍有效。如果在mark后读取超过readlimit字节数据,mark标记就会失效,调用reset()方法会有异常。

但实际的运行情况却和JAVA文档中的描述并不完全相符。 有时候在BufferedInputStream类中调用mark(int readlimit)方法后,即使读取超过readlimit字节的数据,mark标记仍有效,仍然能正确调用reset方法重置。

事实上,mark在JAVA中的实现是和缓冲区相关的。只要缓冲区够大,mark后读取的数据没有超出缓冲区的大小,mark标记就不会失效。如果不够大,mark后又读取了大量的数据,导致缓冲区更新,原来标记的位置自然找不到了。

因此,mark后读取多少字节才失效,并不完全由readlimit参数确定,也和BufferedInputStream类的缓冲区大小有关。 如果BufferedInputStream类的缓冲区大小大于readlimit,在mark以后只有读取超过缓冲区大小的数据,mark标记才会失效。看下面的例子。

private static void test1() {
InputStream is = null;
BufferedInputStream buffer = null;
try {
is = new FileInputStream("/Users/liuxiao/test");//ABCDE
buffer = new BufferedInputStream(is);
//buffer = new BufferedInputStream(is,2);
System.out.println((char) buffer.read());//A
System.out.println((char) buffer.read());//B

buffer.mark(2);// 标记在C的位置,并且设置readlimit为2
System.out.println("mark() invoked");
System.out.println((char) buffer.read());//C
System.out.println((char) buffer.read());//D
System.out.println((char) buffer.read());//E 此时超出了mark设置的readlimit

if (buffer.markSupported()) {
buffer.reset();//这里有可能抛出异常
System.out.println("reset() invoked");
System.out.println((char) buffer.read());//C
System.out.println((char) buffer.read());//D
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (is != null) try {buffer.close();is.close();} catch (IOException e) {e.printStackTrace();}
}
}

输出:

InputStream的mark()、reset()方法_数据

如果将BufferedInputStream缓冲区大小设置成2,再次执行就会抛出异常:

InputStream的mark()、reset()方法_数据_02

简言之,BufferedInputStream类调用mark(int readlimit)方法后读取多少字节标记才失效,是取readlimit和BufferedInputStream类的缓冲区大小两者中的最大值,而并非完全由readlimit确定。这个在JAVA文档中是没有提到的。

3、ByteArrayInputStream类调用mark和reset:

ByteArrayInputStream重写了父类InputStream的mark和resetf方法,其有支持 mark 和 reset 方法的能力。

ByteArrayInputStream中,定义了一个byte数组来存放流数据(俗称缓冲区数组),和BufferedInputStream不同的是,ByteArrayInputStream的byte数组包含了整个字节数组,无法在构造函数中指定该缓冲区大小。这也就意味着,ByteArrayInputStream的mark方法参数readlimit根本就没有用,调用mark方法的时候写多少都无所谓(因为byte缓冲区缓冲了整个字节数组,无论mark后读取多少,都不会超过缓冲区大小)

public class ByteArrayInputStream extends InputStream {

protected byte buf[];
protected int pos;
protected int mark = 0;
protected int count;

public ByteArrayInputStream(byte buf[]) {
this.buf = buf;
this.pos = 0;
this.count = buf.length;
}

public boolean markSupported() {
return true;
}

public void mark(int readAheadLimit) {
mark = pos;
}

public synchronized void reset() {
pos = mark;
}
}

看一个例子:

private static void test0() {
InputStream inputStream = new ByteArrayInputStream("ABCDE".getBytes());

try {
System.out.println((char) inputStream.read());//A
System.out.println((char) inputStream.read());//B

inputStream.mark(2);// 标记在C的位置,设置readlimit=2
System.out.println("mark() invoked");
System.out.println((char) inputStream.read());//C
System.out.println((char) inputStream.read());//D
System.out.println((char) inputStream.read());//E 超出了mark的readlimit

if (inputStream.markSupported()) {
inputStream.reset();//不会抛出异常,因为ByteArrayInputStream缓冲了整个字节数组
System.out.println("reset() invoked");
System.out.println((char) inputStream.read());//C
System.out.println((char) inputStream.read());//D
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {inputStream.close();} catch (IOException e) {e.printStackTrace();}
}
}

无论makr的参数是多少,都会输出:

InputStream的mark()、reset()方法_数据_03

参考:

​https://developer.aliyun.com/article/364045​