文章目录
我们知道,每次从 InputStream 对象中读取数据后,其当前读取位置的指针就会发生移动。如果在读取完的某时还想重新从此 InputStream 对象中读取数据,但此指针已到尽头,因此无法做到这一点。另外,InputStream 还不支持克隆,这意味着也不能事先备份这个 InputStream 对象。不过,这并不是没有办法。
方法 1
如果使用的 InputStream 对象支持方法 mark,可以联合方法 reset 来进行 InputStream 对象的读取重置。方法是,先使用方法 mark 标记一个位置,然后之后在需要重置的时候,使用方法 reset 来将此 InputStream 对象重置到刚才的位置。
方法 mark 的使用规则如下。当 InputStream 对象调用方法 mark 时,它会在此 InputStream 对象的当前读取位置做一个标记。不过,它需要提供一个参数,这个参数是一个读取范围。在标记后继续从 InputStream 中读取数据时,如果读取到的数据大小超过了这个范围,则之后调用方法 reset 时将直接抛出异常。可以看出,在此处,这个参数没有太大的作用,最好设置为大于需要读取的数据大小,一般设置为在此 InputStream 对象的方法 available 的返回值即可。
使用模板如下:
// 如果此 inputStream 支持方法 mark
if (inputStream.markSupported()) {
try {
inputStream.mark(inputStream.available()); // 在当前位置作标记
} catch (IOException ioException) {
ioException.printStackTrace(); // TODO:处理异常
}
// TODO:从此 InputStream 对象中读取数据
try {
inputStream.reset(); // 将 inputStream 重置
} catch (IOException ioException) {
ioException.printStackTrace(); // TODO:处理异常
}
// TODO:重新开始从此 InputStream 对象中读取数据
try {
inputStream.reset(); // 将 inputStream 再次进行重置,以供以后可能进行的再次读取
} catch (IOException ioException) {
ioException.printStackTrace(); // TODO:处理异常
}
} else {
// TODO:如果不支持方法 mark,使用其它的办法
}
【踩坑提醒】
如果 InputStream 的对应的数据源是一种超大文件或是一种源源不断的数据流,则上面代码中关于 InputStream 多个方法都是不准确的。具体来说,这种情况下,
InputStream.available()
返回的是 InputStream 缓冲区中的值,因此此值不能代表日后可能会读取到的最大数据量。实际上,它总是会小于该值。InputStream.mark(int readlimit)
只会在未来读取到的字节小于 readlimit 才会有效。一旦超过,此值无效。InputStream.reset()
的理由同InputStream.mark(int readlimit)
。
因此,对于超大文件或源源不断的数据流,建议不要使用本方法。使用本方法会变得无效。
方法 2
如果使用的 InputStream 对象不支持方法 mark(比如,FileInputStream 就不支持此方法),可以考虑使用经典的方法,这种方法类似于深克隆的通用方法。可以先将此 InputStream 对象转化为另一种只读的独立的数据类型,然后再用此数据类型不断生成所需的 InputStream 对象。这种方法有很多,比方说,先将 InputStream 对象转化为 byte 数组,然后创建一个 InputStream 对象来读取此 byte 数组。但是,这种方法需要在一开始就从 InputStream 中读取完全部的数据,这样就会失去 InputStream 对象惰性读取的优势,因此最好使用上面的 方法 1
。这里给出借助 byte 数组来完成这一功能的办法。
byte 数组与 InputStream 的相互转化
byte 数组转 InputStream
public static byte[] inputStream2byteArray(InputStream inputStream) throws IOException {
return inputStream.readAllBytes();
}
InputStream转 byte 数组
public static InputStream byteArray2InputStream(byte[] bytes) {
return new ByteArrayInputStream(bytes);
}
实现反复读 InputStream
byte[] bytes = new byte[0];
try {
bytes = inputStream2byteArray(inputStream);
} catch (IOException ioException) {
ioException.printStackTrace(); // TODO:处理异常
}
// TODO:当需要 InputStream 时,可以直接获得一个新的 InputStream。
var needInputStream = byteArray2InputStream(bytes);
// TODO:反复获得 InputStream
var needInputStream2 = byteArray2InputStream(bytes);
// TODO:...
方法 3
重新创建流。这是最推荐的方法。在一种超大文件或是源源不断的数据流时,方法 1
的重置可能会无效,而 方法 2
读取并备份整个数据源这种操作会过度消耗大量内存。对于超大文件或源源不断的数据流,方法 1
和 方法 2
都是不可行的。
重新创建数据流的方法不唯一,且受限于具体的场景。一般都是通过一种 URI 来创建流。