如何反复读取同一个 InputStream 对象

  我们知道,每次从 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 来创建流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值