异步方法中处理文件流对象的问题

问题: 当接口中传递文件流给后端处理时, 可能会面临两个问题 :
1, 文件流处理一般时间较长, 为了避免接口长时间等待,我们一般在异步线程中处理文件流, 但是当同步线程已经返回, 我们在异步线程中使用该流对象就会遇见一个问题: IOException: stream closed(文件流已关闭)
2, 文件流对象在读取之后, 读取指针会不断移动, 对于不支持mark and reset操作的流对象,我们就会遇到一个文件流无法二次读取的问题

1, 将流通道中的数据保存到本地文件中, 解决异步线程流关闭的问题

将输入流中的数据保存到本地文件中的方法有多种, 这里介绍两种常用的方式:

  1. 使用 jdk自带的 Files.copy()方法
  Files.copy(inputStream, Paths.get(targetPath), StandardCopyOption.REPLACE_EXISTING);
  1. 使用commons-io包下的FileUtils工具类(可以自动关流)
	FileUtils.copyFile(inputSteam ,new File(targetPath));
2, 对文件流对象进行拷贝,解决文件流不可重用的问题
  1. 哪些流对象执行mark and reset (注意: 要想重复读取流中的数据就只能在最后一次读取流数据结束之后才关流, 提前关流会造成后面的流读取报错)
    对于处于次级的流对象 FileInputstream, FilterInpustream都没有重写父级 Inpustream接口的mark and reset 方法, 如果调用是直接抛出异常.
    在这里插入图片描述
    如果要使用这个两个方法, 必须是 BufferedInputStream 或者 ByteArrayInputStream, 因为这两个流对象重写Inpustream接口中的mark and reset方法
    在这里插入图片描述

使用场景举例:
对于前端上传的文件, 后端为了防止文件攻击, 需要校验上传的文件类型, 为了获取真实的文件类型, 我们需要读取文件流中前28个byte, 但是校验不能影响后续的业务逻辑, 这样就需要对文件流进行重复读取, MultipartFile对象获取的流对象是支持mark and reset操作

        // 校验文件类型;
        String fileType;
        InputStream inputStream; // 不要关闭这个流, 后续还会继续读取
        try {
            inputStream = file.getInputStream();
            if (inputStream.markSupported()) {
                inputStream.mark(30); // multipartFile中获取的流对象支持mark and reset, 这里就不作判断
                fileType = FileTypeUtil.getType(inputStream, originalFilename);
                inputStream.reset();
            } else {
                fileType = FileNameUtil.getSuffix(originalFilename).toLowerCase(Locale.ROOT);
            }
        } catch (IOException e) {
            log.error("获取文件流异常:", e);
            throw new AppException("获取文件流异常");
        }
  1. 对于无法调用 mark and reset方法的流对象, 如果我们想要重复读取该流通道中的数据, 就需要对流对象进行拷贝
		InputStream inputStream = new FileInputStream(targetPath);

//        boolean flag = inputStream.markSupported();
//        System.out.println(flag);

        // 创建字节数组输出流对象
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        // 创建 4kb的缓存数组
        byte[] bytes = new byte[4*1024];

        int length;
        while((length = inputStream.read(bytes)) != -1) {
            byteArrayOutputStream.write(bytes,0, length);
            byteArrayOutputStream.flush();
        }

        // 拷贝新的输入流
        InputStream copyInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AutoPoi是一个基于POI开发的Java Excel操作工具,可以方便地生成Excel文件。如果想要异步下载由AutoPoi生成的Excel文件,可以使用Java的多线程来实现。以下是一个简单的异步下载AutoPoi生成的Excel文件的示例: ```java import com.alibaba.excel.EasyExcel; import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.OutputStream; import java.net.URLEncoder; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @Service public class ExcelService { private final ExecutorService executorService = Executors.newFixedThreadPool(10); public void downloadExcelAsync(HttpServletResponse response) { executorService.submit(() -> { String fileName = "example.xlsx"; try (OutputStream out = response.getOutputStream()) { response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("utf-8"); response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")); List<Object> dataList = getDataList(); //获取数据列表 EasyExcel.write(out).sheet("Sheet1").doWrite(dataList); } catch (IOException e) { e.printStackTrace(); } }); } public void shutdown() { executorService.shutdown(); } private List<Object> getDataList() { //获取数据列表的方法 //... } } ``` 在上面的代码,我们使用了Java的ExecutorService来创建一个线程池,然后将Excel下载任务提交给线程池异步执行。方法`downloadExcelAsync`接收一个HttpServletResponse对象作为参数,用于向浏览器发送Excel文件。在方法内部,我们设置响应的Content-Type、Content-Disposition和字符编码,然后使用EasyExcel将数据列表写入到输出。最后,在`catch`代码块处理可能发生的异常。方法`shutdown`用于关闭线程池。 要使用`ExcelService`类,可以像下面这样调用它: ```java @Autowired private ExcelService excelService; @GetMapping("/downloadExcel") public void downloadExcel(HttpServletResponse response) { excelService.downloadExcelAsync(response); } ``` 在上面的代码,我们注入了一个`ExcelService`对象,并在Controller调用`downloadExcelAsync`方法异步下载Excel文件。需要注意的是,如果多次调用`downloadExcelAsync`方法,则会创建多个线程池,因此应该在退出应用程序之前调用`shutdown`方法关闭线程池。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值