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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值