一种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) {// 重置responseresponse.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导出信息")@SneakyThrowspublic 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导出")@SneakyThrowspublic 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导出信息")@SneakyThrowspublic 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导出信息")@SneakyThrowspublic 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.NotNullprivate 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,所以可能有错误,希望大佬们能够多多指点。
相关文章:

一种excel多线程并发写sheet的方案
一、背景 有一次项目的需求要求导出excel,并且将不同的数据分别写到不同的sheet中。 二、 方案概述 首先一开始使用easyexcel去导出excel,结果发现导出时间需要3秒左右。于是想着能不能缩短excel导出时间,于是第一次尝试使用异步线程去查询数…...

深入了解接口测试:揭秘网络分层和数据处理!
网络分层和数据 上一小节中介绍了接口测试中一些必要重要的定义,这一节我们来讨论一下在学习接口测试过程中我们要关注的最重要的东西:网络分层和数据。 首先,我们来尝试理解一下,为什么网络是要分层的呢? 我们可以…...
Java并发编程
进程和线程 进程即程序的一次执行过程,各个进程之间是独立的。线程是更小的单位,一次进程中,可能会有多个线程,可能会相互影响,各个线程有自己的程序计数器,虚拟机栈和本地方法栈,同时共同使用…...

vue+echarts实现依赖关系无向网络拓扑结图节点折叠展开策略
目录 引言 一、设计 1. 树状图(不方便呈现节点之间的关系,次要考虑) 2. 力引导依赖关系图 二、力引导关系图 三、如何实现节点的Open Or Fold 1. 设计逻辑 节点展开细节 节点收缩细节 代码实现 四、结果呈现 五、完整代码 引言 我…...

Unity3d 灯光阴影开启,法线贴图出现BUG
URP项目打开灯光的阴影后,法线贴图出现BUG 解决方案:按照下图所示调整材质的选项即可...

c语言:模拟实现atoi函数
atoi函数的功能和用法: 主要功能:将字符串转换为整数。例如,将字符类型的“123”转换为整数123. #include <stdio.h> #include <stdlib.h>int main() {char str[] "123";int num atoi(str);printf("Converted …...

Docker 使用心得
创建一个docker 镜像,相关运行代码,放在docker镜像文件同级, pm2 不能与 docker一起使用() # node 服务docker FROM node:10.16.3LABEL author"sj"RUN mkdir -p /var/nodeCOPY ./node /var/nodeWORKDIR /va…...

Nacos 架构原理
基本架构及概念 服务 (Service) 服务是指一个或一组软件功能(例如特定信息的检索或一组操作的执行),其目的是不同的客户端可以为不同的目的重用(例如通过跨进程的网络调用)。Nacos 支持主流的服务生态,…...
尝试修改vim光标的思路
尝试修改vim光标,失败 想让vim的光标在不同模式下显示不同样式 尝试了很多方法,但是没有作用 " Set cursor shape and color if &term ~ "xterm"" INSERT modelet &t_SI "\<Esc>[6 q" . "\<Esc&…...

SpringBoot整合Activiti7——消息事件(十)
文章目录 消息事件开始事件中间事件边界事件代码实现xml文件测试流程流程执行步骤 消息事件 消息事件只有一个接收者,消息具有名字与载荷。 信息会储存在 act_ru_event_subscr 表中。 <!-- 定义消息 --> <message id"msgId1" name"msgName…...
高翔:《自动驾驶与机器人中的SLAM技术 》-Slam_in_autonomous_driving 编译过程中遇到的问题
使用的环境是ubuntu20.04 问题1.安装g2o没有问题,不过在编译整个项目工程时候报错: ”openmp_mutex.h: 30:10: fatal error: g2o/config.h: No such file or directory“: 解决办法: 只需要将/thirdparty/g2o/build/g2o下的config.h放到/…...
org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder 实现密码加密 验证 代码示例
BCryptPasswordEncoder 是 Spring Security 提供的用于加密和验证密码的实现类。它使用强大的 BCrypt 散列函数来存储密码的散列值,提供了一种安全的密码存储方法。以下是一个简单的示例代码,演示如何使用 BCryptPasswordEncoder 进行密码加密和验证&…...

《微信小程序开发从入门到实战》学习三十八
4.2 云开发JSON数据库 4.2.9 条件查询与查询指令 在查询数据时,有时需要对查找的数据添加一些限定条件,只获取满足给定条件的数据,这样的查询称为条件查询。 可以在集合引用上使用where方法指定查询条件,再用get方法࿰…...

云服务器哪家便宜?亚马逊AWS等免费云服务器推荐
在这数字化的时代,云计算技术越来越广泛应用于各种场景,尤其是云服务器,作为一种全新的服务器架构正在逐渐取代传统的物理服务器,“云服务器哪家便宜”等用户相关问题也受到越来越多的关注。自从亚马逊最早推出了首个云计算服务—…...

Linux删除了大文件为什么磁盘空间没有释放?
某天,收到监控系统的告警信息,说磁盘空间占用过高,登上服务器,使用 df -h 一看,发现磁盘占用率已经 96%了: 通过查看 /usr/local/nginx/conf/vhost/xxx.conf 找到 access_log 和 error_log 的路径&#x…...
编写bat脚本执行msyql建库sql
使用cmd命令执行(windows下) 【MySQL的bin目录】\mysql –u用户名 –p密码 –D数据库<【sql脚本文件路径全名】,示例: D:\mysql\bin\mysql –uroot –p123456 -Dtest<d:\test\ss.sql 注意: A、如果在sql脚本文件中使用了use 数据库&…...

【JavaSE学习专栏】第04篇 Java面向对象
文章目录 1 面向过程&面向对象2 类和对象2.1 对象的特征2.2 java类及类的成员2.3 类的语法格式 3 创建与初始化对象3.1 类的成员之一:属性3.2 类的成员之二:方法3.3 类的成员之三:构造器(构造方法)3.3.1 无参构造方…...

sCrypt 在英国伦敦 Exeter 大学讲学
6月5日,sCrypt CEO晓晖和他的两位同事在英国伦敦Exeter大学举行了一场精彩的讲座。刘晓晖向听众们详细介绍了sCrypt智能合约开平台,并演示了如何使用sCrypt来开发基于比特币的智能合约。他用生动形象的语言,深入浅出地解释了这个领域复杂而又…...

人工智能基础创新的第二增长曲线
编者按:2023年是微软亚洲研究院建院25周年。借此机会,我们特别策划了“智启未来”系列文章,邀请到微软亚洲研究院不同研究领域的领军人物,以署名文章的形式分享他们对人工智能、计算机及其交叉学科领域的观点洞察及前沿展望。希望…...
华为OD机试真题-分割均衡字符串-2023年OD统一考试(C卷)
题目描述: 均衡串定义:字符串只包含两种字符,且两种字符的个数相同。 给定一个均衡字符串,请给出可分割成新的均衡子串的最大个数。 约定字符串中只包含大写的X和Y两种字符。 输入描述: 均衡串:XXYYXY 字符…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...

如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...

短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...

深度学习水论文:mamba+图像增强
🧀当前视觉领域对高效长序列建模需求激增,对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模,以及动态计算优势,在图像质量提升和细节恢复方面有难以替代的作用。 🧀因此短时间内,就有不…...