一种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 字符…...

【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...

对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...

如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...

招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...

基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...
多模态图像修复系统:基于深度学习的图片修复实现
多模态图像修复系统:基于深度学习的图片修复实现 1. 系统概述 本系统使用多模态大模型(Stable Diffusion Inpainting)实现图像修复功能,结合文本描述和图片输入,对指定区域进行内容修复。系统包含完整的数据处理、模型训练、推理部署流程。 import torch import numpy …...