一种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 字符…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...
算法:模拟
1.替换所有的问号 1576. 替换所有的问号 - 力扣(LeetCode) 遍历字符串:通过外层循环逐一检查每个字符。遇到 ? 时处理: 内层循环遍历小写字母(a 到 z)。对每个字母检查是否满足: 与…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
STM32---外部32.768K晶振(LSE)无法起振问题
晶振是否起振主要就检查两个1、晶振与MCU是否兼容;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容(CL)与匹配电容(CL1、CL2)的关系 2. 如何选择 CL1 和 CL…...
