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();
// 关闭 ByteArrayOutputStream
byteArrayOutputStream.close();
// 使用 byte[] 创建 ByteArrayInputStream
return 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;
}
}
}