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;}}
}