小言_互联网的博客

【JAVA基础】重复使用同一输入流

338人阅读  评论(0)

博主在工作中遇到问题:需要对读入的文件 (MultipartFile) 计算 MD5,同时又需要将其上传到 S3上,即需要对同一输入流进行操作,但是按照流本身所代表的抽象含义,数据一旦流过去,就无法被再次使用。这里给出三种解决的方法:

1. 将输入流转换为文件

这种方式最容易想到,既然需要多次使用,就可以将流转为文件,写入磁盘中,需要的时候再从磁盘读取文件,缺点在于从磁盘写入和读取较为耗时。代码如下:

	public void useInputStreamTwiceBySaveToDisk(InputStream inputStream) {
		// 文件存放的路径
        String desPath = "test001.bin";
        try (BufferedInputStream is = new BufferedInputStream(inputStream);
             BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(desPath))) {
            int len;
            byte[] buffer = new byte[1024];
            while ((len = is.read(buffer)) != -1) {
                os.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 需要使用时,通过文件流读取磁盘文件进行
        File file = new File(desPath);
        StringBuilder sb = new StringBuilder();
        try (BufferedInputStream is = new BufferedInputStream(new FileInputStream(file))) {
            int len;
            byte[] buffer = new byte[1024];
            while ((len = is.read(buffer)) != -1) {
                sb.append(new String(buffer, 0, len));
            }
            System.out.println(sb.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

2. 将输入流转化为数据

可以通过把流中的全部数据读取到一个字节数组中,再通过访问字节数组来获取我们需要的字节信息。例如我们可以利用构建一个 ByteArrayOutputStream 来保留输入流的信息,而需要使用时,通过构造 ByteArrayInputStream 对象来获取相应的 InputStream。代码如下:

 	public void useInputStreamTwiceSaveToByteArrayOutputStream(InputStream inputStream) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 第一次获取 InputStream
        InputStream inputStream1 = new ByteArrayInputStream(outputStream.toByteArray());
        printInputStreamData(inputStream1);

        // 第二次获取 InputStream
        InputStream inputStream2 = new ByteArrayInputStream(outputStream.toByteArray());
        printInputStreamData(inputStream2);
    }

3. 利用输入流的标记与重置

对于 InputStream 类的子类 BufferedInputStream,其在 InputStream 类的基础上提供了内部缓冲区来提升性能,同时提供了对标记和重置的支持。通过在流开始的地方进行标记,当一个接收者读取完流中的内容之后,进行重置即可。重置完成之后,流的当前读取位置又回到了流的开始,就可以再次使用。代码如下:

	public void useInputStreamTwiceByUseMarkAndReset(InputStream inputStream) {
        StringBuilder sb = new StringBuilder();
        try (BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream, 10)) {
            byte[] buffer = new byte[1024];
            // 调用 mark方法来进行标记
            // 这里设置的标记在重置之后允许读取的字节数是整数的最大值
            bufferedInputStream.mark(bufferedInputStream.available() + 1);
            int len;
            while ((len = bufferedInputStream.read(buffer)) != -1) {
                sb.append(new String(buffer, 0, len));
            }
            System.out.println(sb.toString());
            // 在第一次调用结束后,显式地调用 reset方法进行流的重置操作
            bufferedInputStream.reset();

			// 第二次对流进行读取
            sb = new StringBuilder();
            int len1;
            while ((len1 = bufferedInputStream.read(buffer)) != -1) {
                sb.append(new String(buffer, 0, len1));
            }
            System.out.println(sb.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

存在问题

  • 对于将输入流转换为文件,缺点显而易见就是需要进行磁盘的读写,影响了速率
  • 而对于后面两种,将输入流转化为数据还是利用输入流的标记与重置,两者实际上都是将内容保存到一个byte[]中。对于输入流转化为数据,比较明显可以看出其底层是byte[],文件的内容将被缓存在这里。而对于利用输入流的标记与重置,BufferedInputStream 提供了内部缓存区来增加读取速度,而要实现流的重置,也是利用了该缓存区,如果当可重置的范围大于缓存区大小时,继续读入文件时会对缓存区进行扩容,因此,本质上,也是通过利用一个byte[]进行记录。但是,如果文件内容太大的话,或者服务的内存设定较小时,会导致 GC 频繁,CPU 也将吃紧。

结论

  • 如果文件较大,建议还是直接存入磁盘中,虽然磁盘读写占据了部分时间,但是内存相对安全。
  • 如果文件有限制大小跟数目,可考虑用后面两种方式,因为是直接存放在内存中,速度更快些。
  • 尽量考虑如何避免重复使用读入流,例如我开篇提到的,我需要校验前端传入的 md5 是否正确,同时需要将文件上传到 S3,我最后利用 S3 提供的 api,md5 跟文件流直接传给 S3,由 S3 对 md5 进行校验,并存入相应的桶中。

转载:https://blog.csdn.net/Applying/article/details/104763226
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场