[JAVA]使用ZipOutputStream压缩多个excel

这篇具有很好参考价值的文章主要介绍了[JAVA]使用ZipOutputStream压缩多个excel。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

背景

项目需要实现一个下载接口,将数据根据条件分类,满足同一个条件的数据写入一个excel里,最终将所有excel打包成一个压缩包。

实现思路

  1. 打开一个ZipOutputStream 
  2. 新建Workbook
  3. 将一组数据写入Workbook
  4. 将Workbook写入ZipOutputStream 
  5. 重复步骤2至步骤5直至写完所有数据
  6. 关闭ZipOutputStream 

注意

步骤4中将Workbook中的数据写入ZipOutputStream时不能直接调用Workbook的write()方法,因为write()方法最终会关闭它写入的输出流

原因分析

当我们只往输出流里写一个Workbook时,用write()方法很方便,因为写完之后输出流再也不会用到了。但是当我们用反复调用这个方法往输出流里写多个Workbook的时候,你会惊喜的发现,欸,异常了!Stream closed!!!写多少个表就报多少次异常!(当然准确地说是n-1个,因为写第一个的时候流还是开着的)

java.io.IOException: Stream closed
	at java.util.zip.ZipOutputStream.ensureOpen(ZipOutputStream.java:97) ~[na:1.8.0_60]
	at java.util.zip.ZipOutputStream.putNextEntry(ZipOutputStream.java:190) ~[na:1.8.0_60]

从输出的异常信息来看,是在执行ZipOutputStream.ensureOpen()的时候出错了,我们可以进源码看一下触发这个异常的条件。

    private boolean closed = false;
    private void ensureOpen() throws IOException {
        if (closed) {
            throw new IOException("Stream closed");
        }
    }

可以看到ZipOutputStream有一个标记"closed",表示流是否被关闭,默认是false。而ensureOpen()只有在closed为true时才会抛出异常。那么谁能修改这个标记呢?只有ZipOutputStream.close()。

    public void close() throws IOException {
        if (!closed) {
            super.close();
            closed = true;
        }
    }

由于我们并没有手动调用close()方法,且closed已经被修改,那么就说明这个方法在别的地方被调用了。我们来看一下Workbook的write()方法,关注传入的输出流是怎么被关闭的。

    public void write(OutputStream stream) throws IOException {
        Iterator i$ = this._xFromSxHash.values().iterator();

        while(i$.hasNext()) {
            SXSSFSheet sheet = (SXSSFSheet)i$.next();
            sheet.flushRows();
        }

        File tmplFile = TempFile.createTempFile("poi-sxssf-template", ".xlsx");

        try {
            FileOutputStream os = new FileOutputStream(tmplFile);

            try {
                this._wb.write(os);
            } finally {
                os.close();
            }
            
            //可以看到write()写数据的时候调用了injectData()
            this.injectData(tmplFile, stream);
        } finally {
            if (!tmplFile.delete()) {
                throw new IOException("Could not delete temporary file after processing:" + tmplFile);
            }

        }

    }
    private void injectData(File zipfile, OutputStream out) throws IOException {
        ZipFile zip = new ZipFile(zipfile);

        try {
            //创建了out的包装流zos
            ZipOutputStream zos = new ZipOutputStream(out);

            InputStream is;
            try {
                for(Enumeration en = zip.entries(); en.hasMoreElements(); is.close()) {
                    ZipEntry ze = (ZipEntry)en.nextElement();
                    zos.putNextEntry(new ZipEntry(ze.getName()));
                    is = zip.getInputStream(ze);
                    XSSFSheet xSheet = this.getSheetFromZipEntryName(ze.getName());
                    if (xSheet != null) {
                        SXSSFSheet sxSheet = this.getSXSSFSheet(xSheet);
                        InputStream xis = sxSheet.getWorksheetXMLInputStream();

                        try {
                            copyStreamAndInjectWorksheet(is, zos, xis);
                        } finally {
                            xis.close();
                        }
                    } else {
                        copyStream(is, zos);
                    }
                }
            } finally {

                //这里关闭了包装流zos
                zos.close();
            }
        } finally {
            zip.close();
        }

    }

其实关闭包装流的时候节点流就被关闭了,具体原因可以参考:Java IO包装流如何关闭? - 嗨,你的益达~~~ - 博客园 (cnblogs.com)

概括一下就是Java中流的创建使用的是装饰者模式,执行装饰类的方法时最终会调用被装饰类的对应方法。

正确写法

那么如何才能实现往一个ZipOutputStream中写入多个excel呢?

我们可以思考一下,既然问题是Workbook的write()方法最终关闭了输出流引起的,那么是不是可以模仿write()方法的思路,只要最后去掉关闭流的操作就可以了!

由于源码中的实现思路比较复杂,感兴趣的可以自己研究。我在这里给出一种简便的写法,核心就是将workbook数据写入临时文件,最后再将临时文件数据写入ZipOutputStream。这样write()关闭的就是临时文件的输出流,ZipOutputStream一直保持开启状态。当然最后记得自己把它关了,或者用try-with-resource。

为了方便大家看效果,我直接写了个controller,大家可以自己写个web项目测试一下。这里贴一张自测效果图。

java导出zip,压缩多个xlsx文件,java文章来源地址https://www.toymoban.com/news/detail-775707.html

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.springframework.http.HttpHeaders;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 多个excel压缩为一个压缩包
 */
@Slf4j
@RestController
@RequestMapping("/zip")
public class WorkbookCompress {

    private final static int BUFFER_READ_SIZE = 10000;

    @GetMapping("/excel")
    public void compressExcels(HttpServletResponse response) {
        List<SXSSFWorkbook> workbooks = getWorkbooks();
        try(ServletOutputStream servletOutputStream = response.getOutputStream();
            ZipOutputStream zipOutputStream = new ZipOutputStream(servletOutputStream)) {

            response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename= test.zip");
            response.setHeader(HttpHeaders.CONTENT_TYPE, "application/zip");

            SXSSFWorkbook wk1 = workbooks.get(0);
            Workbook wk2 = workbooks.get(1);

            //写第一个文件
            zipOutputStream.putNextEntry(new ZipEntry(getName(wk1)+".xlsx"));
            File tempFile = new File(getName(wk1));
            FileOutputStream fos = new FileOutputStream(tempFile);
            wk1.write(fos);
            wk1.close();
            FileInputStream fis = new FileInputStream(tempFile);
            StreamUtils.copy(fis, zipOutputStream);
            fis.close();
            tempFile.delete();
            zipOutputStream.closeEntry();

            //写第二个文件
            zipOutputStream.putNextEntry(new ZipEntry(getName(wk2)+".xlsx"));
            tempFile = new File(getName(wk2));
            fos = new FileOutputStream(tempFile);
            wk2.write(fos);
            wk2.close();
            fis = new FileInputStream(tempFile);
            StreamUtils.copy(fis, zipOutputStream);
            fis.close();
            tempFile.delete();
            zipOutputStream.closeEntry();

        } catch (Exception e) {
            log.warn("error occurred, ", e);
        }
    }


    public static List<SXSSFWorkbook> getWorkbooks() {

        /*create workbooks*/
        String[] keys = {"id", "name"};
        //create data
        List<Map<String, Object>> logs = new ArrayList<>();
        Map<String, Object> log1 = new HashMap<>(2);
        log1.put("id", 111);
        log1.put("name", "aaa");
        Map<String, Object> log2 = new HashMap<>(2);
        log2.put("id", 222);
        log2.put("name", "bbb");
        Map<String, Object> log3 = new HashMap<>(2);
        log3.put("id", 333);
        log3.put("name", "aaa");
        Map<String, Object> log4 = new HashMap<>(2);
        log4.put("id", 444);
        log4.put("name", "bbb");
        logs.add(log1);
        logs.add(log2);
        logs.add(log3);
        logs.add(log4);
        //split
        Map<String, List<Map<String, Object>>> results = logs.stream()
            .collect(Collectors.groupingBy(log -> log.get("name").toString()));
        List<Map<String, Object>> result = results.get("aaa");

        //write to workbooks
        SXSSFWorkbook wk1 = (SXSSFWorkbook)createWorkBook(results.get("aaa"), keys, keys);
        SXSSFWorkbook wk2 = (SXSSFWorkbook)createWorkBook(results.get("bbb"), keys, keys);

        ArrayList<SXSSFWorkbook> workbooks = new ArrayList<>();
        workbooks.add(wk1);
        workbooks.add(wk2);

        return workbooks;
    }

    public static String getName(Workbook wk) {
        Sheet sheet = wk.getSheetAt(0);
        if (sheet != null) {
            Row row = sheet.getRow(1);
            Cell cell = row.getCell(1);
            String value = cell.getStringCellValue();
            return value;
        }
        return "default";
    }

    /**
     * 创建excel文档
     * @param list 数据
     * @param keys list中map的key数组集合
     * @param columnNames excel的列名
     * */
    public static Workbook createWorkBook(List<Map<String, Object>> list, String[] keys, String[] columnNames) {

        // 创建excel工作簿
        // HSSFWorkbook每个sheet最大数据量为65536, 而且内存占用高, 这里改为SXSSFWorkbook, edited By jianghan on V4.2
        Workbook wb = new SXSSFWorkbook(BUFFER_READ_SIZE);

        // 创建第一个sheet
        Sheet sheet = wb.createSheet("sheet1");

        // 设置列宽, 第一个参数表示要为第几列设, 第二个参数表示列的宽度。
        for (int i=0; i<keys.length; i++) {
            sheet.setColumnWidth(i, (int) 35.7 * 150);
        }

        // 创建第一行
        Row row = sheet.createRow(0);

        // 写入列名
        for (int i=0; i < columnNames.length; i++) {
            Cell cell = row.createCell(i);
            cell.setCellValue(columnNames[i]);
        }

        // 写入各行各列的值
        if (!CollectionUtils.isEmpty(list)) {
            for (int i = 0; i < list.size(); i++) {

                Row row1 = sheet.createRow(i+1);

                for (int j=0; j< keys.length; j++) {
                    Cell cell = row1.createCell(j);
                    cell.setCellValue(list.get(i).get(keys[j]) == null ? " " : list.get(i).get(keys[j]).toString());
                }
            }
        }
        return wb;
    }
}

到了这里,关于[JAVA]使用ZipOutputStream压缩多个excel的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • java 批量下载将多个文件(minio中存储)压缩成一个zip包

    我的需求是将minio中存储的文件按照查询条件查询出来统一压成一个zip包然后下载下来。 思路:针对这个需求,其实可以有多个思路,不过也大同小异,一般都是后端返回流文件前端再处理下载,也有少数是压缩成zip包之后直接给下载链接返回到前端,前端收到链接url直接

    2024年02月10日
    浏览(55)
  • Java实现导出多个excel表打包到zip文件中,供客户端另存为窗口下载

    业务需求:从数据库查询多个list集合信息封装excel,每个excel都有2个sheet页,填充不同的信息,最后将所有excel打包成zip文件,以流的形式返回给客户端,供客户端另存为窗口下载。 只发出一次请求 每个excel表中到数据记录不能超过2条 excel文件或者zip包不会上传服务器,而是

    2024年02月06日
    浏览(51)
  • Zip压缩文件夹 + 前端导出

    2024年03月21日
    浏览(51)
  • 使用Java IO进行压缩和解压缩 | ZIP和GZIP的实现

      Java IO中的压缩和解压缩功能主要通过 java.util.zip 包和 java.util.jar 包来实现,并具有以下作用和优势: 方便易用 :Java提供了简洁而易于使用的API,使得压缩和解压缩变得简单和方便。 多种压缩算法支持 :Java提供了多种压缩算法,如ZIP、GZIP、JAR等,可以根据不同的需求

    2024年02月15日
    浏览(46)
  • 前端调接口下载(导出)后端返回.zip压缩文件流(的坑!)

    前言:基于vue2+element-ui的一个后台管理系统,需求评审要加一个导入导出文件的功能,由于可能导出的数据量过大(几十万条数据),下载时间过长,所以用.zip压缩文件替代excel文件 本人以前也做过导出文件的功能,但是用的方法是后端处理数据然后放到另一个服务器上,前

    2024年02月03日
    浏览(48)
  • Mysql备份命令Mysqldump导入、导出以及压缩成zip、gz格式

    命令:mysqldump -u用户名 -p数据库密码 数据库名 文件名 如果用户名需要密码,则需要在此命令执行后输入一次密码核对;如果数据库用户名不需要密码,则不要加“-p”参数,导入的时候相同。注意输入的用户名需要拥有对应数据库的操作权限,否则无法导出数据。由于是作

    2024年02月11日
    浏览(42)
  • 前端下载文化部几种方法(excel,zip,html,markdown、图片等等)和导出 zip 压缩包

    使用 后端的设置 Content-Type: application/octet-stream(下载用的流) 使用导出 zip 如果这篇【文章】有帮助到你💖,希望可以给我点个赞👍,创作不易,如果有对前端或者对python感兴趣的朋友,请多多关注💖💖💖,咱们一起探讨和努力!!! 👨‍🔧 个人主页 : 前端初见

    2024年02月14日
    浏览(52)
  • Java压缩与解压缩ZIP文件

    在现代计算机上,数据传输和存储越来越依赖于文件压缩技术。当我们需要发送大量数据时,压缩文件可以大大减少传输时间和网络带宽,而且压缩文件还可以帮助我们节省磁盘空间。在Java中提供了压缩和解压缩文件的功能,可以使用java.util.zip包中的类来实现。本篇将对如

    2024年02月09日
    浏览(55)
  • java工具类 - 实现文件压缩zip及解压缩

    对hutool工具类进行的封装 解压缩 zipUtil是hutool包下的工具类 使用:

    2024年02月16日
    浏览(31)
  • java 文件压缩zip【两种方式】

     一、普通方式 二、使用zip4j方式 引入jar:  代码块:

    2024年02月11日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包