当前位置: 首页 > news >正文

Java从zip文件中读取指定的csv文件使用EasyExcel解析出现流关闭异常Stream closed

Java从zip文件中读取指定的csv文件使用EasyExcel解析出现流关闭异常Stream closed

  • 1. 问题描述
  • 2. 异常信息
  • 3. 异常分析
  • 4. 异常复现
  • 5. 解决方案
  • 6. 代码实现

1. 问题描述

Java从zip文件中读取指定的csv文件(zip文件中存在多个csv文件),使用EasyExcel解析出现流关闭异常Stream closed。

2. 异常信息

 * read csv file error: java.io.IOException: Stream closed at* java.base/java.util.zip.ZipInputStream.ensureOpen(ZipInputStream.java:69) at* java.base/java.util.zip.ZipInputStream.getNextEntry(ZipInputStream.java:118) at

3. 异常分析

根据异常堆栈,可以看到异常发生在 ZipInputStream 的 getNextEntry() 方法调用过程中,具体是在 ZipInputStream.ensureOpen() 方法中抛出了 java.io.IOException,原因是“Stream closed”。
异常的根本原因是 ZipInputStream 已经被关闭,但在尝试读取下一个条目时仍然在使用它。

在 EasyExcel.read(zipInputStream, …).doRead(); 之后,zipInputStream 可能已经被 doRead() 方法关闭。
ZipInputStream 在 doRead() 方法中被关闭后,后续调用 getNextEntry() 会抛出异常。

问题的核心是使用EasyExcel的时候会帮我们关闭文件流,使用的时候需要注意。

4. 异常复现

读取zip文件中的文件,预期:EasyExcel执行doRead,关闭zipInputStream,while中第二次获取zipInputStream.getNextEntry()异常。

通过debug发现,只要执行了EasyExcel执行doRead方法,zipInputStream的close状态就会变为true,再次执行zipInputStream.getNextEntry()异常。

5. 解决方案

方案一
将zipInputStream赋值给entryInputStream,使用entryInputStream处理数据。
结论:无效。原因:EasyExcel.read()会同时把entryInputStream与zipInputStream都关闭。
try (InputStream entryInputStream = zipInputStream) {
EasyExcel.read(entryInputStream, getClazz(), new AlipayDetailListener()).charset(Charset.forName(GBK)).sheet().doRead();
}

方案二
将zipInputStream从新拷贝一份,不是简单的赋值,而是将zipInputStream每个字节读取到新的对象流ByteArrayInputStream中,详见handle2。问题解决。

6. 代码实现


import com.alibaba.excel.EasyExcel;
import com.example.shell.excel.AliPayBillExcelModelBO;
import lombok.extern.slf4j.Slf4j;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;@Slf4j
public class ZipFileReaderException {private static final String GBK = "GBK";private static final String BILL_FILE_NAME_END = "_业务明细.csv";/*** Java从zip文件中读取指定的csv文件使用EasyExcel解析出现流关闭异常Stream closed*/public static void main(String[] args) {String filePath = "C:\\Users\\back\\20888310488164410156_202406191-hz1.zip";String filePath2 = "C:\\Users\\back\\20888310488164410156_202406191-hz2.zip";ByteArrayInputStream byteArrayInputStream = convertZipToByteArrayInputStream(filePath);// handle(byteArrayInputStream);handle2(byteArrayInputStream);}/*** 解决关闭流异常实现*/public static void handle2(InputStream inputStream) {try (ZipInputStream zipInputStream = new ZipInputStream(inputStream, Charset.forName("GBK"))) {ZipEntry entry;while ((entry = zipInputStream.getNextEntry()) != null) {log.info("aliPay bill data zipFileName: {}", entry.getName());if (entry.getName().endsWith(BILL_FILE_NAME_END)) {ByteArrayInputStream byteArrayInputStream = convertZipInputStreamToByteArrayInputStream(zipInputStream);EasyExcel.read(byteArrayInputStream, getClazz(), new AlipayDetailListener()).charset(Charset.forName(GBK)).sheet().doRead();}}} catch (IOException e) {log.error("read csv file error: ", e);}}/*** 该方式会出现关闭流异常问题*/public static void handle(InputStream inputStream) {try (ZipInputStream zipInputStream = new ZipInputStream(inputStream, Charset.forName(GBK))) {ZipEntry entry;// 获取压缩文件中的每个子文件while ((entry = zipInputStream.getNextEntry()) != null) {log.info("bill data zipFileName:{}", entry.getName());if (entry.getName().endsWith(BILL_FILE_NAME_END)) {// 解决方案,不关闭zipInputStream,使用entryInputStream处理数据try (InputStream entryInputStream = zipInputStream) {EasyExcel.read(entryInputStream, getClazz(), new AlipayDetailListener()).charset(Charset.forName(GBK)).sheet().doRead();}}}} catch (IOException e) {log.error("read csv file error: ", e);}}public static Class<AliPayBillExcelModelBO> getClazz() {return AliPayBillExcelModelBO.class;}/*** 将ZipInputStream转为ByteArrayInputStream*/public static ByteArrayInputStream convertZipInputStreamToByteArrayInputStream(ZipInputStream zipInputStream) throws IOException {ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int bytesRead;// 读取 ZipInputStream 中的内容while ((bytesRead = zipInputStream.read(buffer)) != -1) {byteArrayOutputStream.write(buffer, 0, bytesRead);}// 将 ByteArrayOutputStream 转换为 byte[]byte[] data = byteArrayOutputStream.toByteArray();// 关闭 ByteArrayOutputStreambyteArrayOutputStream.close();// 使用 byte[] 创建 ByteArrayInputStreamreturn new ByteArrayInputStream(data);}/*** 将文件转为ByteArrayInputStream*/public static ByteArrayInputStream convertZipToByteArrayInputStream(String filePath) {System.out.println("filePath:" + filePath);try (FileInputStream fileInputStream = new FileInputStream(new File(filePath))) {byte[] buffer = new byte[fileInputStream.available()];fileInputStream.read(buffer);return new ByteArrayInputStream(buffer);} catch (IOException e) {e.printStackTrace();return null;}}
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【常见算法题】斐波那契数列(矩阵快速幂)
  • 歌曲爬虫下载
  • java多线程(初阶)
  • 【Godot4自学手册】第四十五节用着色器(shader)制作水中效果
  • linux C语言remove函数及相关函数
  • 如何选择较为安全的第三方依赖版本?
  • [C++][opencv]基于opencv实现photoshop算法可选颜色调整
  • 微前端架构下的应用版本回退策略与实践
  • C语言 | Leetcode C语言题解之第341题扁平化嵌套列表迭代器
  • idea付费插件,哪个比较好用?
  • 书生浦语大模型全链路开源开放体系学习
  • Docker和虚拟机的区别详细讲解
  • Android T about screen rotation(二)
  • spring boot 接收第三方mq消息
  • 基于JAVA美容院管理系统(源码+论文+讲解等)
  • Angular Elements 及其运作原理
  • exports和module.exports
  • HTTP那些事
  • Markdown 语法简单说明
  • MD5加密原理解析及OC版原理实现
  • Spring声明式事务管理之一:五大属性分析
  • 聊聊flink的TableFactory
  • 让你的分享飞起来——极光推出社会化分享组件
  • 想写好前端,先练好内功
  • ​二进制运算符:(与运算)、|(或运算)、~(取反运算)、^(异或运算)、位移运算符​
  • # 消息中间件 RocketMQ 高级功能和源码分析(七)
  • #100天计划# 2013年9月29日
  • (17)Hive ——MR任务的map与reduce个数由什么决定?
  • (CPU/GPU)粒子继承贴图颜色发射
  • (安卓)跳转应用市场APP详情页的方式
  • (附源码)springboot 校园学生兼职系统 毕业设计 742122
  • (六)Hibernate的二级缓存
  • (三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练
  • (一)、python程序--模拟电脑鼠走迷宫
  • (一)RocketMQ初步认识
  • (译) 理解 Elixir 中的宏 Macro, 第四部分:深入化
  • **PHP二维数组遍历时同时赋值
  • .Net FrameWork总结
  • .net mvc actionresult 返回字符串_.NET架构师知识普及
  • .NET Remoting学习笔记(三)信道
  • .NET/C# 编译期能确定的字符串会在字符串暂存池中不会被 GC 垃圾回收掉
  • .NetCore 如何动态路由
  • /etc/apt/sources.list 和 /etc/apt/sources.list.d
  • ??eclipse的安装配置问题!??
  • @for /l %i in (1,1,10) do md %i 批处理自动建立目录
  • @property @synthesize @dynamic 及相关属性作用探究
  • @require_PUTNameError: name ‘require_PUT‘ is not defined 解决方法
  • @SpringBootApplication 包含的三个注解及其含义
  • @TableLogic注解说明,以及对增删改查的影响
  • [ linux ] linux 命令英文全称及解释
  • [04] Android逐帧动画(一)
  • [AI StoryDiffusion] 创造神奇故事,AI漫画大乱斗!
  • [Android]Android P(9) WIFI学习笔记 - 扫描 (1)
  • [APIO2012] 派遣 dispatching
  • [BT]BUUCTF刷题第4天(3.22)