一种excel多线程并发写sheet的方案

news2025/2/24 2:39:28

一、背景

        有一次项目的需求要求导出excel,并且将不同的数据分别写到不同的sheet中。

二、 方案概述

        首先一开始使用easyexcel去导出excel,结果发现导出时间需要3秒左右。于是想着能不能缩短excel导出时间,于是第一次尝试使用异步线程去查询数据库,却发现接口的时间并没有明显缩短,于是自己就开始排查耗时的操作,于是发现是写sheet的时候是串行执行,并且每个写sheet的时间并不短,尤其在sheet比较多的时候,会导致导出的时间比较长。于是,想着能不能使用异步线程并发去写sheet,但是,使用的时候报错。后来去找报错的原因,是因为easyexcel并不支持并发写。于是,我就转战POI。尝试是否能够并发写入多个sheet。

使用easyexcel写入多个sheet

        try {
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
            String fileName = URLEncoder.encode(EXCEL, "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
            AtomicInteger atomicInteger = new AtomicInteger(0);
            ExcelWriter build = EasyExcel.write(response.getOutputStream(),VirtualInstanceDataPoint.class).build();
            list.stream().map(i -> CompletableFuture.supplyAsync(() -> {
                        return service.list();
                    }, executor)).collect(Collectors.toList()).stream()
                    .map(CompletableFuture::join).collect(Collectors.toList()).forEach(r->{
                        int andIncrement = atomicInteger.getAndIncrement();
                        WriteSheet build1 = EasyExcel.writerSheet(andIncrement, r.get(0).getDevice() + andIncrement).build();
                        build.write(r, build1);
                    });
            build.finish();
            response.flushBuffer();
        } catch (Exception e) {
            // 重置response
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            response.getWriter().println(JSON.toJSONString(R.error().message(e.getMessage()).code(20007)));
}

并发写入多个sheet会报

org.apache.poi.openxml4j.exceptions.PartAlreadyExistsException: A part with the name '/xl/worksheets/sheet1.xml' already exists : Packages shall not contain equivalent part names and package implementers shall neither create nor recognize packages with equivalent part names. [M1.12]

 POI写入多个sheet

    String[] EXPORT_HEADER = {"head1","head2"};
    @GetMapping("export3")
    @ApiOperation(value = "excel导出信息")
    @SneakyThrows
    public void export3(HttpServletResponse response) {
        OutputStream outputStream = response.getOutputStream();
        response.reset();
        response.setContentType("application/vnd.ms-excel");
        response.setHeader("Content-disposition", "attachment;filename=template.xls");
        AtomicInteger atomicInteger = new AtomicInteger();
        HSSFWorkbook workbook = new HSSFWorkbook();
        Font font = workbook.createFont();
        font.setBold(true);
        HSSFCellStyle cellStyle = workbook.createCellStyle();
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        cellStyle.setFont(font);
        List<IndexData> indexDatas = new ArrayList<>();
        indexDatas.add(new IndexData("1",1.1));
        indexDatas.add(new IndexData("2",2.2));
        indexDatas.add(new IndexData("3",3.3));
        for (IndexData indexData : indexDatas) {
            HSSFSheet sheet = workbook.createSheet(indexData.getStr());
            HSSFRow row = sheet.createRow(0);
            // 创建表头
            for (int i = 0; i < EXPORT_HEADER.length; i++) {
                HSSFCell cell = row.createCell(i);
                cell.setCellValue(EXPORT_HEADER[i]);
                cell.setCellStyle(cellStyle);
            }
            row = sheet.createRow(1);
            row.createCell(0).setCellValue(indexData.getStr());
            row.createCell(1).setCellValue(indexData.getDoubleData());
        }

        workbook.write(outputStream);
        outputStream.flush();
        outputStream.close();
    }
    static  class IndexData {
        public IndexData(String string, Double doubleData) {
            this.str = string;
            this.doubleData = doubleData;
        }

        public String getStr() {
            return str;
        }

        public Double getDoubleData() {
            return doubleData;
        }

        private String str;

        private Double doubleData;
    }

POI多线程写多个sheet

    String[] EXPORT_HEADER = {"head1","head2"};
    @GetMapping("export3")
    @ApiOperation(value = "excel导出")
    @SneakyThrows
    public void export3(HttpServletResponse response) {
        OutputStream outputStream = response.getOutputStream();
        response.reset();
        response.setContentType("application/vnd.ms-excel");
        response.setHeader("Content-disposition", "attachment;filename=template.xls");
        AtomicInteger atomicInteger = new AtomicInteger();
        HSSFWorkbook workbook = new HSSFWorkbook();
        Font font = workbook.createFont();
        font.setBold(true);
        HSSFCellStyle cellStyle = workbook.createCellStyle();
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        cellStyle.setFont(font);
        List<IndexData> indexDatas = new ArrayList<>();
        indexDatas.add(new IndexData("1",1.1));
        indexDatas.add(new IndexData("2",2.2));
        indexDatas.add(new IndexData("3",3.3));
        indexDatas.stream().map(data ->CompletableFuture.runAsync(() ->{
            HSSFSheet sheet = workbook.createSheet(data.getStr());
            HSSFRow row = sheet.createRow(0);
            // 创建表头
            for (int i = 0; i < EXPORT_HEADER.length; i++) {
                HSSFCell cell = row.createCell(i);
                cell.setCellValue(EXPORT_HEADER[i]);
                cell.setCellStyle(cellStyle);
            }
            row = sheet.createRow(1);
            row.createCell(0).setCellValue(data.getStr());
            row.createCell(1).setCellValue(data.getDoubleData());
        })).collect(Collectors.toList()).stream().map(CompletableFuture::join).collect(Collectors.toList());
        workbook.write(outputStream);
        outputStream.flush();
        outputStream.close();
    }
    static  class IndexData {
        public IndexData(String string, Double doubleData) {
            this.str = string;
            this.doubleData = doubleData;
        }

        public String getStr() {
            return str;
        }

        public Double getDoubleData() {
            return doubleData;
        }

        private String str;

        private Double doubleData;
    }

但是有时候会报错

java.lang.IllegalArgumentException: calculated end index (2576) is out of allowable range (2564..2569)

是因为在 子线程中创建sheet产生并发。

一个解决方案是主线程预先创建sheet

另一个方案是为创建sheet的操作加锁

    @GetMapping("export1")
    @ApiOperation(value = "excel导出信息")
    @SneakyThrows
    public void export2(HttpServletResponse response) {
        OutputStream outputStream = response.getOutputStream();
        response.reset();
        response.setContentType("application/vnd.ms-excel");
        response.setHeader("Content-disposition", "attachment;filename=template.xls");
        AtomicInteger atomicInteger = new AtomicInteger();
        HSSFWorkbook workbook = new HSSFWorkbook();
        Font font = workbook.createFont();
        font.setBold(true);
        HSSFCellStyle cellStyle = workbook.createCellStyle();
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        cellStyle.setFont(font);
        list.stream().map(instanceId -> CompletableFuture.runAsync(() -> {
                    List<> collect =  service.list();
                    HSSFSheet sheet = workbook.createSheet(collect.get(0).getDevice()+ atomicInteger.getAndIncrement());
                    HSSFRow row = sheet.createRow(0);
                    // 创建表头
                    for (int i = 0; i < EXPORT_HEADER.length; i++) {
                        HSSFCell cell = row.createCell(i);
                        cell.setCellValue(EXPORT_HEADER[i]);
                        cell.setCellStyle(cellStyle);
                    }
                    for (int i = 0; i < collect.size(); i++) {
                        row = sheet.createRow(i + 1);
                        row.createCell(0).setCellValue(collect.get(i).getInstanceId());
                        row.createCell(1).setCellValue(collect.get(i).getDevice());
                        row.createCell(2).setCellValue(collect.get(i).getDataId());
                        row.createCell(3).setCellValue(collect.get(i).getDataName());
                    }
                }, executor)).collect(Collectors.toList()).stream()
                .map(CompletableFuture::join).collect(Collectors.toList());
        workbook.write(outputStream);
        outputStream.flush();
        outputStream.close();
        
    }

以下使用加锁方式

    String[] EXPORT_HEADER = {"head1","head2"};
    @GetMapping("export3")
    @ApiOperation(value = "excel导出信息")
    @SneakyThrows
    public void export3(HttpServletResponse response) {
        OutputStream outputStream = response.getOutputStream();
        response.reset();
        response.setContentType("application/vnd.ms-excel");
        response.setHeader("Content-disposition", "attachment;filename=template.xls");
        AtomicInteger atomicInteger = new AtomicInteger();
        HSSFWorkbook workbook = new HSSFWorkbook();
        Font font = workbook.createFont();
        font.setBold(true);
        HSSFCellStyle cellStyle = workbook.createCellStyle();
        cellStyle.setAlignment(HorizontalAlignment.CENTER);
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        cellStyle.setFont(font);
        List<IndexData> indexDatas = new ArrayList<>();
        indexDatas.add(new IndexData("1",1.1));
        indexDatas.add(new IndexData("2",2.2));
        indexDatas.add(new IndexData("3",3.3));
        indexDatas.stream().map(data ->CompletableFuture.runAsync(() ->{
            HSSFSheet sheet = getSheet(data, workbook);
            HSSFRow row = sheet.createRow(0);
            // 创建表头
            for (int i = 0; i < EXPORT_HEADER.length; i++) {
                HSSFCell cell = row.createCell(i);
                cell.setCellValue(EXPORT_HEADER[i]);
                cell.setCellStyle(cellStyle);
            }
            row = sheet.createRow(1);
            row.createCell(0).setCellValue(data.getStr());
            row.createCell(1).setCellValue(data.getDoubleData());
        })).collect(Collectors.toList()).stream().map(CompletableFuture::join).collect(Collectors.toList());
        workbook.write(outputStream);
        outputStream.flush();
        outputStream.close();
    }

    @org.jetbrains.annotations.NotNull
    private synchronized static HSSFSheet getSheet(IndexData data, HSSFWorkbook workbook) {
        HSSFSheet sheet = workbook.createSheet(data.getStr());
        return sheet;
    }

但是这种方式还是会有一些并发带来的错误。

java.lang.NullPointerException: null
    at org.apache.poi.hssf.record.SSTSerializer.serialize(SSTSerializer.java:70)
    at org.apache.poi.hssf.record.SSTRecord.serialize(SSTRecord.java:279)
    at org.apache.poi.hssf.record.cont.ContinuableRecord.getRecordSize(ContinuableRecord.java:60)
    at org.apache.poi.hssf.model.InternalWorkbook.getSize(InternalWorkbook.java:1072)
    at org.apache.poi.hssf.usermodel.HSSFWorkbook.getBytes(HSSFWorkbook.java:1474)
    at org.apache.poi.hssf.usermodel.HSSFWorkbook.write(HSSFWorkbook.java:1386)
    at org.apache.poi.hssf.usermodel.HSSFWorkbook.write(HSSFWorkbook.java:1374)

但是在本机实测100个线程10个循环出错的个数在20-30之间

我们可以捕获这些错误使用do while循环,当出错的时候可以清空状态再次重试。

总结:

        该方法只是本菜鸡的愚见,使用这种方式的确可以完成并发写sheet,缩短接口的相应速度,将3秒左右的接口降低到50ms左右。应该能适合sheet略多,但并发量、数据量不多的excel导出,但本人也是第一次使用POI,所以可能有错误,希望大佬们能够多多指点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1275207.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

weblogic任意文件上传漏洞(CVE-2018-2894)

任务一&#xff1a; 复现环境中的漏洞 任务二&#xff1a; 上传webshell或者反弹shell&#xff0c;并执行whoami。 任务一&#xff1a; 1.环境搭建&#xff0c;发现需要密码&#xff0c;所以我们去日志里面查看管理员密码。 2.了解一下这个平台&#xff0c;然后进行一些基本配…

数据结构——图解链表OJ题目

学完了单链表之后&#xff0c;我们对其基本结构已经有了一定的了解&#xff0c;接下来我们通过一些题目强化对链表的理解&#xff0c;同时学习一些面试笔试题目的新思路以及加强对数据结构单链表的掌握。 目录 题目一.876. 链表的中间结点 - 力扣&#xff08;LeetCode&#x…

金蝶云星空单据界面新增状态,操作明细行的新增按钮时判断表头基础资料是否必录

文章目录 金蝶云星空单据界面新增状态&#xff0c;操作明细行的新增按钮时判断表头基础资料是否必录BOS配置代码实现 金蝶云星空单据界面新增状态&#xff0c;操作明细行的新增按钮时判断表头基础资料是否必录 BOS配置 四种方式都不生效。 代码实现 表单插件的BeforeDoOpera…

最短路算法

文章目录 最短路总览朴素Dijkstra - 稠密图 - O ( n 2 ) O(n^2) O(n2)具体思路时间复杂度分析使用场景AcWing 849. Dijkstra求最短路 ICODE 堆优化 D i j k s t r a Dijkstra Dijkstra 算法 - 稀疏图 - O ( m l o g n ) O(mlogn) O(mlogn)具体思路和时间复杂度分析使用场景A…

九章量子计算机:引领量子计算的新篇章

九章量子计算机:引领量子计算的新篇章 一、引言 随着科技的飞速发展,量子计算已成为全球科研领域的前沿议题。九章量子计算机作为中国自主研发的量子计算机,具有划时代的意义。本文将深入探讨九章量子计算机的原理、技术特点、应用前景等方面,带领读者领略量子计算的魅力…

11.28~11.29基本二叉树的性质、定义、复习;排序算法;堆

完全二叉树&#xff08;Complete Binary Tree&#xff09;是一种特殊的二叉树结构&#xff0c;它具有以下特点&#xff1a; 所有的叶子节点都集中在树的最后两层&#xff1b;最后一层的叶子节点都靠左排列&#xff1b;除了最后一层&#xff0c;其他层的节点数都达到最大值。 …

短剧行业@2023:狂飙、刹车与新生

【潮汐商业评论/原创】 “豪门复仇”“先婚后爱”“重生”“穿越”&#xff0c;Ashley几乎每次回家路过保安亭&#xff0c;都能看到大叔在看这类上头小短剧&#xff0c;就连有时候在公司&#xff0c;也能听到保洁阿姨在看类似的视频。 久而久之&#xff0c;从好奇到“入坑&am…

利用ElementUI配置商品的规格参数

商品有不同的规格组合&#xff0c;自动生成对应规格的所有组合&#xff0c;并设置该规格的图片、价格、库存数据。 <template><div class"sku-list"><template v-if"!disabled"><div class"sku-list-head"><el-but…

Zigbee—基于Z-STACK组网

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;チノカテ—ヨルシカ 0:46━━━━━━️&#x1f49f;──────── 4:08 &#x1f504; ◀️ ⏸ ▶️ ☰ &a…

nginx配置反向代理及负载均衡

目录 1.前端发送的请求&#xff0c;是如何请求到后端服务的1.nginx 反向代理的好处&#xff1a;2.nginx 反向代理的配置方式&#xff1a;3. nginx 负载均衡的配置方式 1.前端发送的请求&#xff0c;是如何请求到后端服务的 1.nginx 反向代理的好处&#xff1a; 提高访问速度 因…

如何使用 NFTScan NFT API 在 Starknet 网络上开发 Web3 应用

Starknet 是由以色列软件公司 StarkWare 开发的免许可的第 2 层网络。Starknet 作为以太坊上的 ZK Rollup 运行&#xff0c;帮助 dApp 使用 STARK 证明以更低的交易成本实现更大的计算规模。该网络允许智能合约与区块链上部署的其他合约进行交互&#xff0c;从而提高协议之间的…

大数据Doris(三十一):Doris简单查询

文章目录 Doris简单查询 一、简单查询 二、​​​​​​​Join

基于Springboot的在线问卷调查系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的在线问卷调查系统(有报告)。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring…

golang WaitGroup的使用与底层实现

使用的go版本为 go1.21.2 首先我们写一个简单的WaitGroup的使用代码 package mainimport ("fmt""sync" )func main() {var wg sync.WaitGroupwg.Add(1)go func() {defer wg.Done()fmt.Println("xiaochuan")}()wg.Wait() }WaitGroup的基本使用场…

Peter算法小课堂—差分与前缀和

差分 Codeforces802 D2C C代码详解 差分_哔哩哔哩_bilibili 一维差分 差分与前缀和可以说成减法和加法的关系、除法和乘法的关系、积分和微分的关系&#xff08;听不懂吧&#xff09; 给定数组A&#xff0c;S为A的前缀和数组&#xff0c;则A为S的差分数组 差分数组构造 现…

Unittest(1):unittest单元测试框架简介setup前置初始化和teardown后置操作

unittest单元测试框架简介 unittest是python内置的单元测试框架&#xff0c;具备编写用例、组 织用例、执行用例、功能&#xff0c;可以结合selenium进行UI自动化测 试&#xff0c;也可以结合appium、requests等模块做其它自动化测试 官方文档&#xff1a;https://docs.pytho…

opencv 图像边框

cv.copyMakeBorder() 图像设置边框或者填充

HarmonyOs 4 (一) 认识HarmonyOs

目录 一 HarmonyOs 背景1.1 发展时间线1.2 背景分析1.2.1 新场景1.2.2 新挑战1.2.3 鸿蒙生态迎接挑战 二 HarmonyOS简介2.1 OpenHarmony2.2 HarmonyOS Connect2.3 HarmonyOS Next**2.4 ArkTS &#xff08;重点掌握&#xff09;****2.5 ArkUI** 三 鸿蒙生态应用核心技术理念**3.…

c/c++概念辨析-指针常量常量指针、指针函数函数指针、指针数组数组指针

概念澄清&#xff1a; 统一规则&#xff1a; 不管是XX指针&#xff0c;还是指针XX&#xff0c;后者是本体&#xff0c;前者只是个定语&#xff0c;前者也可以替换为其他同类&#xff08;例如字符串&#xff09;&#xff0c;帮助理解。 XX指针&#xff1a; 可简单理解为&#…

骨传导耳机是智商税吗?骨传导耳机是利用什么原理听歌?

骨传导耳机并非智商税&#xff0c;而是一种新兴的技术产品。作为是一种新型的听音设备&#xff0c;它采用了与传统耳机不同的工作原理&#xff0c;通过将声音通过骨骼传导到内耳&#xff0c;实现了不用堵塞耳道就能听到声音的效果。相比传统耳机&#xff0c;骨传导耳机具有一些…