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

一、背景

        有一次项目的需求要求导出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.mfbz.cn/a/207845.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

什么是JVM的内存模型?详细阐述Java中局部变量、常量、类名等信息在JVM中的存储位置

导航&#xff1a; 【Java笔记踩坑汇总】Java基础JavaWebSSMSpringBootSpringCloud瑞吉外卖/黑马旅游/谷粒商城/学成在线设计模式面试题汇总性能调优/架构设计源码-CSDN博客 目录 一、JVM基本介绍 二、JVM内存模型 2.0 概述 2.1 类加载子系统 2.2 运行时数据区 2.2.0 基本…

【C++笔记】红黑树封装map和set

一、map和set的泛型封装逻辑 map和set的底层都是红黑树&#xff0c;所以我们想要用红黑树封装map和set的第一个问题就来了&#xff0c;因为set是key结构而map是key-value结构&#xff0c;怎样用同一个底层结构去封装出两个不同存储结构的容器呢&#xff1f;难道我们要将红黑树…

算法题--排椅子(贪心)

题目链接 code #include<bits/stdc.h> using namespace std;struct node{int indx;//用来存储数组下标int cnt;//用来计数 };bool cmp(node a,node b){ //判断是否是数字最大的一个就是经过最多谈话人的道return a.cnt>b.cnt; } node row[2010],cow[2010];bool cmp…

LLM算法工程师面试题总结

一、请简述对大模型的基本原理和架构的理解。 大型语言模型如GPT&#xff08;Generative Pre-trained Transformer&#xff09;系列是基于自注意力机制的深度学习模型&#xff0c;主要用于处理和生成人类语言。下面简要概述了它们的一些基本原理和架构特点&#xff1a; 基本原…

C/C++---------------LeetCode第876. 链表的中间结点

链表的中间结点 题目及要求双指针在main内使用 题目及要求 给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个中间结点。 示例 1&#xff1a; 示例 2&#xff1a; 双指针 思路&#xff1a;分别定义快慢指针…

「Qt Widget中文示例指南」如何创建一个计算器?(二)

Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写&#xff0c;所有平台无差别运行&#xff0c;更提供了几乎所有开发过程中需要用到的工具。如今&#xff0c;Qt已被运用于超过70个行业、数千家企业&#xff0c;支持数百万设备及应用。 本文将展示如何使用…

淘宝/天猫商品详情API接口丨京东商品详情丨1688商品详情丨接口key密钥获取方式

要获取淘宝/天猫商品详情API接口、京东商品详情API接口、1688商品详情API接口以及接口密钥&#xff08;Key&#xff09;&#xff0c;可以按照以下步骤进行操作&#xff1a; 注册并登录淘宝/天猫开发者中心或京东开放平台或1688开放平台&#xff0c;并创建应用。在创建应用的过…

Human3.6m数据处理(mhformer代码解读)

对于3d人体姿态估计任务中数据集human3.6m的处理 写在最前面&#xff1a;这是我自己的理解&#xff0c;说的不一定对。 human3.6m有很多格式的数据&#xff0c;包括视频、2d ground truth、3d ground truth&#xff0c;还分为xyz坐标的表示形式和旋转向量表示形式&#xff0c;这…

HarmonyOS应用开发者基础认证考试(98分答案)

基于最近大家都在考这个应用开发者基础认证考试&#xff0c;因此出了一期&#xff0c;一样复制word里面搜索做&#xff0c;很快&#xff0c;当然good luck 判断题 Ability是系统调度应用的最小单元,是能够完成一个独立功能的组件。一个应用可以包含一个或多个Ability。 正确(Tr…

Swift构造器继承链

类类型的构造器代理 Swift构造器需遵循以下三大规则&#xff1a; 指定构造器必须调用它直接父类的指定构造器方法便利构造器必须调用同一个类中定义的其他初始化方法便利构造器在最后必须调用一个指定构造器 两段式构造过程 Swift 中类的构造过程包含两个阶段。第一个阶段&a…

Redis主从与哨兵架构详解

目录 主从架构 主从环境搭建 主从复制流程 1. 全量复制 2. 部分复制 主从风暴 哨兵架构 概念 哨兵环境搭建 主从架构 主从环境搭建 1. 复制一份redis.conf文件, 修改下面几行配置 port 6380 pidfile /var/run/redis_6380.pid logfile "6380.log" dir /usr/…

PowerDesigner数据库建模软件的安装

解压&#xff1a; 解压好以后&#xff0c;点击PowerDesigner.exe安装 这个安装的版本是15 选择安装路径&#xff0c;可以默认可以自定义&#xff1a; 直接点next&#xff1a; 全选了 点击next&#xff1a; 点击next&#xff1a; 点finish 汉化&#xff1a; 先把pojie和汉化文件…

Unity中Shader编译目标级别

文章目录 前言一、Shader Model二、Shader编译目标级别法1&#xff1a; #pragma target 3.0法2&#xff1a;#pragma require integers geometry 三、测试代码 前言 针对不同平台的特性&#xff0c;所做的一些功能 一、Shader Model ShaderModel 由微软提出&#xff0c;要求显…

蔚碳科技联合中投会发布国内首个 ESG 尽职调查服务标准

11 月 26 日&#xff0c;蔚碳&#xff08;上海&#xff09;科技有限公司&#xff08;以下简称“蔚碳科技”&#xff09;受邀出席由深圳市人民政府主办&#xff0c;深圳市发展和改革委员会、深圳市生态环境局、龙岗区人民政府共同承办的 2023 碳达峰碳中和论坛暨深圳国际低碳城论…

使用 SDKMAN 管理多版本本地 Java 环境---Centos8 Windows

文章目录 windows 安装centos8 安装卸载sdkman使用 windows 安装 SDKMAN是一个 jdk 多版本管理工具&#xff0c;类似于 nodejs 中的 nvm。可以在本地存在多个 java 环境&#xff0c;快速切换功能&#xff0c;同时&#xff0c;他不止于 java sdk&#xff0c;还有maven、tomcat等…

WhatsApp群发消息脚本功能介绍及代码分享!

随着社交媒体的普及&#xff0c;通讯应用成为了人们日常沟通的主要工具之一&#xff0c;其中&#xff0c;WhatsApp凭借其简洁、易用的特点&#xff0c;成为了全球广受欢迎的通讯应用之一&#xff0c;除了基础的聊天功能&#xff0c;WhatsApp还提供了一系列辅助工具功能&#xf…

关于 ls -s 输出文件大小的单位问题的讨论

自己看书正好看到这里&#xff0c;正纳闷呢&#xff0c;上网查了下&#xff0c;发现不是我自己在为这个问题感到困惑。 有个大哥提出一个问题&#xff1a; 问题标题&#xff1a; ls -s的单位到底是什么&#xff1f; man ls -s, --size print the alloca…

web前端之css变量的妙用、通过JavaScrip改变css文件中的属性值、querySelector、setProperty

MENU 效果图htmlJavaScripstylequerySelectorsetProperty 效果图 html <div id"idBox" class"p_r w_680 h_160 b_1s_red"><div id"idItem" class"p_a l_0 t_30 w_100 h_100 bc_rgba_255_00_05 radius_50_"></div> …

matlab 基于卡尔曼滤波的GPS-INS的数据融合的导航

1、内容简介 略 25-可以交流、咨询、答疑 2、内容说明 基于卡尔曼滤波的GPS-INS的数据融合的导航 "基于卡尔曼滤波的GPS-INS的数据融合的导航 基于卡尔曼滤波实现GPS-INS组合导航系统" 卡尔曼滤波、GPS、INS、数据融合、导航 3、仿真分析 4、参考论文 略 …

YOLOv8-Seg改进:SENetV2,squeeze和excitation全面升级,效果优于SENet | 2023年11月最新成果

🚀🚀🚀本文改进: SENetV2,squeeze和excitation全面升级,作为注意力机制引入到YOLOv8,放入不同网络位置实现涨点 🚀🚀🚀YOLOv8-seg创新专栏:http://t.csdnimg.cn/KLSdv 学姐带你学习YOLOv8,从入门到创新,轻轻松松搞定科研; 1)手把手教你如何训练YOLOv8-s…