问题复现
- InputStream流只能被读取一次,一但被读取出来后,再无法再次读取
- 示例
@Slf4j
public class CheckInputStream {
public static void main(String[] args) {
try (
InputStream in = new FileInputStream("logs.log");
) {
byte[] read = read(in);
String s = new String(read);
log.info("第一次内容为:{}", s);
byte[] read2 = read(in);
String s2 = new String(read2);
log.info("第二次读取内容为:{}", s2);
} catch (Exception e) {
e.printStackTrace();
}
}
public static byte[] read(InputStream in) throws IOException {
int available = in.available();
byte[] bytes = new byte[available];
in.read(bytes);
return bytes;
}
}
- 输出结果:从下图可以看出第二次输出内容为空
原因
- 网络连接中的数据通常是通过InputStream读取的。虽然InputStream本身并不限制只能读取一次,但是在网络连接中,一旦数据被读取,它就会被消耗掉,因此无法再次读取相同的数据。这是因为网络连接中的数据是通过数据包进行传输的,一旦数据包被读取,它就会从网络缓冲区中移除,无法再次读取。即使使用mark()和reset()方法,也无法保证能够重新读取之前的数据,因为网络连接的数据包可能已经被丢弃或者移动到了下一个位置。
- 文件读取,InputStream流只能读取一次是因为在读取数据的过程中,流的指针会逐渐向文件的末尾移动。一旦指针到达了文件的末尾,再次读取数据时就无法再次从文件的开头开始读取。因此,InputStream流只能读取一次。
解决
- 将数据流,转成byte数组流,实现数据可重复读取
- 可重复读取原因:因为将数据byte数组形式存储在内存中,且ByteArrayInputStream内部源码实现了重复读取byte数组中的数据
- 即:使用reset()方法,将数组读取idx重置为数组起始位置,然后再次读取还是从数组idx=0位置重新读取
public static void testReadAgain(InputStream in) throws IOException {
byte[] origin = read(in);
ByteArrayInputStream byteIn = new ByteArrayInputStream(origin);
byte[] read1 = read(byteIn);
String s1 = new String(read1);
log.info("第一次通过ByteArrayInputStream流读取数据为:{}", s1);
byteIn.reset();
byte[] read2 = read(byteIn);
String s2 = new String(read2);
log.info("第二次通过ByteArrayInputStream流读取数据为:{}", s2);
}
- 打印结果
总结
- InputStream流是否能够重复读取数据,不是由InputStream决定,而是它的实现类决定
- 比如:FileInputStream实现了InputStream
- FileInputStream不能重复读取,是因为在处理文件读取时,需要不停的将文件读取的指针往下移动
- 当指针移动到文件末尾时,文件读取完成。那么下次读取时,指针无法再往下移动,导致无法重新读取数据。
- 比如:ByteArrayInputStream 实现InputStream
- ByteArrayInputStream可重复读,是因为其内部存储数据是使用byte数组
- Java中数组长度不可变,且从0开始,那么当需要重复读取时,只需要从0再次读取一遍数组即可
- 比如:自己写一个类,实现InputStream接口
- 自己实现的逻辑里,可以将数据写入到一个临时文件
- 然后,从文件中将数据读取出来
- 如果需要重复读取数据,只需要再次读取该文件即可
- 这样,也实现了InputStream流数据的可重复读取