解决大批量数据导出Excel产生内存溢出的方案

2023-11-17

<script type="text/javascript"></script> POI或者JXL在导出大量数据的时候,由于它们将每一个单元格生都成一个Cell对象,所以很容易导致内存溢出。解决这个问题,唯一的办法是弄清楚Excel的二进制格式(汗),并且用流的方式读写Excel。POI和JXL其实提供了二进制方式读写Excel的API,只是因为缺少文档和实例,所以使用的人不多。我编写了这个简单的合并Excel的类,它只适合合并结构相同的多个Excel文件。好在这个功能已经可以解决数据导出产生OOM的问题:将数据分批导出然后合并。
下面的代码使用POI3.1,合并11个3000多行的文档用时约6秒,我实在找不到更多的测试用的文档了。

Java代码 复制代码
  1. @SuppressWarnings("unchecked")   
  2. public class XlsMergeUtil {   
  3.   private static Logger logger = LoggerFactory.getLogger(XlsMergeUtil.class);   
  4.   
  5.   /**  
  6.    * 将多个Xls文件合并为一个,适用于只有一个sheet,并且格式相同的文档  
  7.    * @param inputs 输入的Xls文件  
  8.    * @param out 输出文件  
  9.    */  
  10.   public static void merge(InputStream[] inputs, OutputStream out) {   
  11.     if (inputs == null || inputs.length <= 1) {   
  12.       throw new IllegalArgumentException("没有传入输入流数组,或只有一个输入流.");   
  13.     }   
  14.   
  15.     List<Record> rootRecords = getRecords(inputs[0]);   
  16.     Workbook workbook = Workbook.createWorkbook(rootRecords);   
  17.     List<Sheet> sheets = getSheets(workbook, rootRecords);   
  18.     if(sheets == null || sheets.size() == 0) {   
  19.       throw new IllegalArgumentException("第一篇文档的格式错误,必须有至少一个sheet");   
  20.     }   
  21.     //以第一篇文档的最后一个sheet为根,以后的数据都追加在这个sheet后面   
  22.     Sheet rootSheet = sheets.get(sheets.size() - 1);    
  23.     int rootRows = getRowsOfSheet(rootSheet); //记录第一篇文档的行数,以后的行数在此基础上增加   
  24.     rootSheet.setLoc(rootSheet.getDimsLoc());   
  25.     Map<Integer, Integer> map = new HashMap(10000);   
  26.   
  27.     for (int i = 1; i < inputs.length; i++) { //从第二篇开始遍历   
  28.       List<Record> records = getRecords(inputs[i]);   
  29.       int rowsOfCurXls = 0;   
  30.       //遍历当前文档的每一个record   
  31.       for (Iterator itr = records.iterator(); itr.hasNext();) {   
  32.         Record record = (Record) itr.next();   
  33.         if (record.getSid() == RowRecord.sid) { //如果是RowRecord   
  34.           RowRecord rowRecord = (RowRecord) record;   
  35.           //调整行号   
  36.           rowRecord.setRowNumber(rootRows + rowRecord.getRowNumber());   
  37.           rootSheet.addRow(rowRecord); //追加Row   
  38.           rowsOfCurXls++; //记录当前文档的行数   
  39.         }   
  40.         //SST记录,SST保存xls文件中唯一的String,各个String都是对应着SST记录的索引   
  41.         else if (record.getSid() == SSTRecord.sid) {   
  42.           SSTRecord sstRecord = (SSTRecord) record;   
  43.           for (int j = 0; j < sstRecord.getNumUniqueStrings(); j++) {   
  44.             int index = workbook.addSSTString(sstRecord.getString(j));   
  45.             //记录原来的索引和现在的索引的对应关系   
  46.             map.put(Integer.valueOf(j), Integer.valueOf(index));   
  47.           }   
  48.         } else if (record.getSid() == LabelSSTRecord.sid) {   
  49.           LabelSSTRecord label = (LabelSSTRecord) record;   
  50.           //调整SST索引的对应关系   
  51.           label.setSSTIndex(map.get(Integer.valueOf(label.getSSTIndex())));   
  52.         }   
  53.         //追加ValueCell   
  54.         if (record instanceof CellValueRecordInterface) {   
  55.           CellValueRecordInterface cell = (CellValueRecordInterface) record;   
  56.           int cellRow = cell.getRow() + rootRows;   
  57.           cell.setRow(cellRow);   
  58.           rootSheet.addValueRecord(cellRow, cell);   
  59.         }   
  60.       }   
  61.       rootRows += rowsOfCurXls;   
  62.     }   
  63.     byte[] data = getBytes(workbook, sheets.toArray(new Sheet[0]));   
  64.     write(out, data);   
  65.   }   
  66.   
  67.   static void write(OutputStream out, byte[] data) {   
  68.     POIFSFileSystem fs = new POIFSFileSystem();   
  69.     // Write out the Workbook stream   
  70.     try {   
  71.       fs.createDocument(new ByteArrayInputStream(data), "Workbook");   
  72.       fs.writeFilesystem(out);   
  73.       out.flush();   
  74.     } catch (IOException e) {   
  75.       e.printStackTrace();   
  76.     } finally {   
  77.       try {   
  78.         out.close();   
  79.       } catch (IOException e) {   
  80.         e.printStackTrace();   
  81.       }   
  82.     }   
  83.   }   
  84.   
  85.   static List<Sheet> getSheets(Workbook workbook, List records) {   
  86.     int recOffset = workbook.getNumRecords();   
  87.     int sheetNum = 0;   
  88.   
  89.     // convert all LabelRecord records to LabelSSTRecord   
  90.     convertLabelRecords(records, recOffset, workbook);   
  91.     List<Sheet> sheets = new ArrayList();   
  92.     while (recOffset < records.size()) {   
  93.       Sheet sh = Sheet.createSheet(records, sheetNum++, recOffset);   
  94.   
  95.       recOffset = sh.getEofLoc() + 1;   
  96.       if (recOffset == 1) {   
  97.         break;   
  98.       }   
  99.       sheets.add(sh);   
  100.     }   
  101.     return sheets;   
  102.   }   
  103.   
  104.   static int getRows(List<Record> records) {   
  105.     int row = 0;   
  106.     for (Iterator itr = records.iterator(); itr.hasNext();) {   
  107.       Record record = (Record) itr.next();   
  108.       if (record.getSid() == RowRecord.sid) {   
  109.         row++;   
  110.       }   
  111.     }   
  112.     return row;   
  113.   }   
  114.      
  115.   static int getRowsOfSheet(Sheet sheet) {   
  116.     int rows = 0;   
  117.     sheet.setLoc(0);   
  118.     while(sheet.getNextRow() != null) {   
  119.       rows++;   
  120.     }   
  121.     return rows;   
  122.   }   
  123.   
  124.   @SuppressWarnings("deprecation")   
  125.   static List<Record> getRecords(InputStream input) {   
  126.     try {   
  127.       POIFSFileSystem poifs = new POIFSFileSystem(input);   
  128.       InputStream stream = poifs.getRoot().createDocumentInputStream("Workbook");   
  129.       return org.apache.poi.hssf.record.RecordFactory.createRecords(stream);   
  130.     } catch (IOException e) {   
  131.       logger.error("IO异常:{}", e.getMessage());   
  132.       e.printStackTrace();   
  133.     }   
  134.     return Collections.EMPTY_LIST;   
  135.   }   
  136.   
  137.   static void convertLabelRecords(List records, int offset, Workbook workbook) {   
  138.   
  139.     for (int k = offset; k < records.size(); k++) {   
  140.       Record rec = (Record) records.get(k);   
  141.   
  142.       if (rec.getSid() == LabelRecord.sid) {   
  143.         LabelRecord oldrec = (LabelRecord) rec;   
  144.   
  145.         records.remove(k);   
  146.         LabelSSTRecord newrec = new LabelSSTRecord();   
  147.         int stringid = workbook.addSSTString(new UnicodeString(oldrec.getValue()));   
  148.   
  149.         newrec.setRow(oldrec.getRow());   
  150.         newrec.setColumn(oldrec.getColumn());   
  151.         newrec.setXFIndex(oldrec.getXFIndex());   
  152.         newrec.setSSTIndex(stringid);   
  153.         records.add(k, newrec);   
  154.       }   
  155.     }   
  156.   }   
  157.   
  158.   public static byte[] getBytes(Workbook workbook, Sheet[] sheets) {   
  159.     // HSSFSheet[] sheets = getSheets();   
  160.     int nSheets = sheets.length;   
  161.   
  162.     // before getting the workbook size we must tell the sheets that   
  163.     // serialization is about to occur.   
  164.     for (int i = 0; i < nSheets; i++) {   
  165.       sheets[i].preSerialize();   
  166.     }   
  167.   
  168.     int totalsize = workbook.getSize();   
  169.   
  170.     // pre-calculate all the sheet sizes and set BOF indexes   
  171.     int[] estimatedSheetSizes = new int[nSheets];   
  172.     for (int k = 0; k < nSheets; k++) {   
  173.       workbook.setSheetBof(k, totalsize);   
  174.       int sheetSize = sheets[k].getSize();   
  175.       estimatedSheetSizes[k] = sheetSize;   
  176.       totalsize += sheetSize;   
  177.     }   
  178.   
  179.     byte[] retval = new byte[totalsize];   
  180.     int pos = workbook.serialize(0, retval);   
  181.   
  182.     for (int k = 0; k < nSheets; k++) {   
  183.       int serializedSize = sheets[k].serialize(pos, retval);   
  184.       if (serializedSize != estimatedSheetSizes[k]) {   
  185.             throw new IllegalStateException("Actual serialized sheet size (" + serializedSize   
  186.             + ") differs from pre-calculated size (" + estimatedSheetSizes[k] + ") for sheet (" + k   
  187.             + ")");   
  188.         Sheet.serializeIndexRecord() does not   
  189.       }   
  190.       pos += serializedSize;   
  191.     }   
  192.     return retval;   
  193.   }   
  194.   
  195.   public static void main(String[] args) throws Exception {   
  196.     final String PATH = "E://projects//java//ws_0//export//data//";   
  197.     InputStream[] inputs = new InputStream[10];   
  198.     inputs[0] = new java.io.FileInputStream(PATH + "07_10.xls");   
  199.     for(int i = 1; i <= 9; i++) {   
  200.       inputs[i] = new java.io.FileInputStream(PATH + "07_0" + i + ".xls");   
  201.     }   
  202.     OutputStream out = new FileOutputStream(PATH + "xx.xls");   
  203.     long t1 = System.currentTimeMillis();   
  204.     merge(inputs, out);   
  205.     System.out.println(System.currentTimeMillis() - t1);//简陋的测试一下时间   
  206.   }   
  207.   
  208. }  
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

解决大批量数据导出Excel产生内存溢出的方案 的相关文章

  • 以点作为分隔符分割字符串

    我想知道我是否要在一个字符串上分割字符串 正确的方式 我的代码是 String fn filename split return fn 0 我只需要字符串的第一部分 这就是我返回第一项的原因 我问这个是因为我在 API 中注意到 意味着任何
  • 如何在数据库中对 (Java) 枚举进行建模(使用 SQL92)

    您好 我正在使用名为 性别 的列对实体进行建模 在应用程序代码中 性别应该是一个 Java 枚举类型 有 2 个值 男性和女性 知道作为数据类型的枚举不是通用 SQL 语言 92 的一部分 您将如何建模它 数据模型必须是可移植的 以便由多个
  • java中队列的实现

    在 Java 中实现队列是一个非常常见的面试问题 我在网上冲浪 看到了许多实现 他们做了一些奇特的事情 比如实现队列接口和编写自己的addLast and removeFirst 方法 我的问题是我不能使用LinkedList 类并使用其预
  • codePointAt 和 charCodeAt 之间的区别

    有什么区别String prototype codePointAt and String prototype charCodeAt 在 JavaScript 中 A codePointAt 65 A charCodeAt 65 从 MDN
  • 通过 JNI 从 Applet 调用 DLL

    我有一个 概念验证 的作品 它跨越了一些不熟悉的领域 我的任务是将 EFTPOS 机器连接到在内联网浏览器中作为小程序运行的应用程序 我暂时忽略了 EFTPOS dll 并用我选择的语言 Delphi 创建了一个简单的 JNI 修饰的 DL
  • 此版本不符合 Google Play 64 位要求,添加库后仍然出现错误

    我正在 Play 商店上传一个视频编辑器应用程序 其中包含带有一些本机代码的库 所以我通过将其添加到 gradle 来使其兼容 64 位 ndk abiFilters armeabi v7a arm64 v8a x86 x86 64 添加了
  • Codility 钉板

    尝试了解 Codility NailingPlanks 的解决方案 问题链接 https app codility com programmers lessons 14 binary search algorithm nailing pla
  • 更改 JTextPane 的大小

    我是Java新手 刚刚在StackOverflow中找到了这段代码 ResizeTextArea https stackoverflow com questions 9370561 enabling scroll bars when jte
  • 如何在Gradle中支持多种语言(Java和Scala)的多个项目?

    我正在尝试将过时的 Ant 构建转换为 Gradle 该项目包含约50个Java子项目和10个Scala子项目 Java 项目仅包含 Java Scala 项目仅包含 Scala 每个项目都是由 Java 和 Scala 构建的 这大大减慢
  • 用于防止滥用的 Servlet 过滤器? (DoS、垃圾邮件等)

    我正在寻找一个 Servlet 过滤器库 它可以帮助我保护我们的 Web 服务免受未经授权的使用和 DDoS 攻击 我们的网络服务有 授权客户 因此理想情况下 过滤器将帮助检测未经授权或行为不当的客户 或检测使用同一帐户的多个人 此外 我们
  • 如何使用 Spring MVC 和 Thymeleaf 添加静态文件

    我的问题是如何添加 CSS 和图像文件等静态文件 以便我可以使用它们 我正在使用 Spring MVC 和 Thymeleaf 我查看了有关此主题的各种帖子 但它们对我没有帮助 所以我才来问 根据这些帖子 我将 CSS 和图像文件放在res
  • 如何在命令提示符中检查 JAVA_OPTS 值?

    我们的应用程序部署 JBoss 服务器然后抛出错误 PermGen space 然后在 jboss bat 和配置文件中设置 permgen 变量中的 java OPTS JAVA OPTs 中是否有值 assige 如何检查 如何在命令提
  • Kerberos 缓存票证

    我使用的是 Windows 7 64 位 我创建了一个简单的应用程序来对实现 PrivilegedAction 的类的 run 方法中的文件进行计数 以下是我的 jaas conf 文件 CountFiles com sun securit
  • Netty中连接关闭后重新连接的最佳方法是什么

    简单场景 扩展 SimpleChannelUpstreamHandler 的较低级别的类 A 此类是发送消息和接收响应的主力 系统其他部分可以使用顶级类 B 来发送和接收消息 可以模拟同步和异步 此类创建 ClientBootstrap 设
  • PHP 中的多行字符串文字

    考虑 xml l xml vv echo xml 这将回响vv 为什么以及如何为诸如此类的事情执行多行字符串文字简单XML https en wikipedia org wiki SimpleXML etc Well xml l vv Wo
  • 替换 JSON 中的转义字符

    我想用空格替换 JSON 字符串中的 字符 我怎样才能做到这一点 我发现从 JSON 字符串中删除所有转义字符的最简单 最好的方法是将字符串传递到正则表达式 Unescape 方法 此方法返回一个没有转义字符的新字符串 甚至删除了 n t
  • 如何将任务添加到 gradle 中的主要“构建”任务

    当我尝试使用以下代码将任务添加到主构建任务时 rootProject tasks getByName build dependsOn mytask 当我跑步时它抱怨gradle w build输出 Where Build file line
  • 设计抽象类时是否应该考虑序列化问题?

    一般来说这个问题来自Eclipse建议在抽象类上添加串行版本UID 由于该类是抽象类 因此该类的实例永远不会存在 因此它们永远不会被序列化 只有派生类才会被序列化 所以我的问题是放置一个安全 SuppressWarnings serial
  • java数据结构模拟数据树

    我需要帮助定义使用什么方法 我有一个 SOAP 响应 给我一个 xml 文件 我需要在屏幕上显示 3 个相关列表 当您在第一个列表中选择一个项目时 相应的选择将出现在第二个列表中 依此类推 我只对从 xml 流中提取数据后如何有效地组织数据
  • 条件查询:按计数排序

    我正在尝试执行一个标准查询 该查询返回 stackoverflow 中回答最多的问题 例如常见问题解答 一个问题包含多个答案 我正在尝试使用标准查询返回按每个问题的答案数排序的回答最多的问题 任何人都知道我应该在 hibernate cri

随机推荐

  • 数据结构与算法 各类数图概念集合

    拓扑排序 有向无环图才能进行拓扑排序 理解 就是在大学期间所有的课程 你只有先学完计算机基础 才能学更加高深的课程 从一个入度为0的点出发 找下一个一直到最后就是拓扑排序 前 中 后序排序 前 根左右 中 左中右 后 左右中 要确定一颗二叉
  • <毕业设计>最适合大学生的12个Java系统项目(附源码)

    就业 毕业设计 Java项目合集 小编给大家整理了12个Java系统项目 附源码 白嫖到底 最合适大学生学习的Java毕业设计教程合集 合集视频教程链接 https www bilibili com video BV1pB4y1h7Pr s
  • [Python爬虫] Selenium获取百度百科旅游景点的InfoBox消息盒

    前面我讲述过如何通过BeautifulSoup获取维基百科的消息盒 同样可以通过Spider获取网站内容 最近学习了Selenium Phantomjs后 准备利用它们获取百度百科的旅游景点消息盒 InfoBox 这也是毕业设计实体对齐和属
  • 1Panel 安装部署

    1Panel 是一个现代化 开源的 Linux 服务器运维管理面板 1 环境要求 安装前请确保您的系统符合安装条件 操作系统 支持主流 Linux 发行版本 基于 Debian RedHat 包括国产操作系统 服务器架构 x86 64 aa
  • Limit

    Mysql limit用法 select from test LIMIT 3 当 limit后面跟一个参数的时候 该参数表示要取的数据的数量 表示直接取前三条数据 以下的两种方式均表示取2 3 4三条条数据 select from test
  • R语言深度学习:智能客服聊天机器人

    目录 一 准备工作 二 数据预处理 三 构建模型 1 准备训练数据 2 构建seq2seq模型
  • Ubuntu18.04安装docker及nvidia docker、NVIDIA Container Toolkit

    1 卸载旧版docker sudo apt get remove docker sudo apt get remove auto remove docker sudo apt remove docker ce 如果上面方法都不行直接 使用d
  • PID控制算法学习与Matlab仿真

    文章目录 起因 算法原理 算法解析 调参小技巧 Matlab仿真 起因 PID控制算法应该是包括工业机器人等各种行业和领域中非常常用的一种控制算法了 了解这个算法的起因是在稚晖君开发的自行车项目中见到 后来在北理工组会中了解到PID控制算法
  • ssrf漏洞描述

    ssrf是一种由攻击者构造请求 由服务端发起请求的安全漏洞 一般情况下 ssrf攻击的目标是外网无法访问的内部系统 ssrf漏洞原理 ssrf的形成大多是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制 例如 服务
  • 玄铁C910总览

    一 开源玄铁C910简介 玄铁C910是由平头哥设计并开源的高性能CPU 基于开源的RISC V指令集 主要面向对性能要求严格的边缘计算领域 如边缘服务器 边缘计算卡 高端机器视觉 高端视频监控 自动驾驶 移动智能终端 5G 基站等 玄铁C
  • SAP-ABAP-DOI技术的优化与说明

    SAP ABAP DOI技术的优化与说明 阅后感 不错的一篇文章 自己收藏下 顺带分享给众人 看完本文章后 大家也可以学习下 DOI 的API文档 当有需求直接下载本地时 可以应用CALL i oi document proxy的一个方法S
  • c# 访问共享文件夹 用户名或密码不正确 及 拒绝访问

    组策略 计算机配置 Windows设置 安全设置 本地策略 安全选项 网络访问 本地帐户的共享和安全模型 改为 经典 本地用户以自己身份验证 不改会出现用户名和密码错误 拒绝访问 安全里添加用户
  • day075:XML的约束:DTD约束文档、DTD约束文档的三种引入方法、DTD语法规则

    目录 一 DTD约束 1 什么是DTD约束 2 创建DTD约束文档的步骤 3 代码示例 4 引入DTD约束文档的三种方法 1 引入本地DTD约束文档 2 在xml文件内部引入 3 从网络引入dtd文件 二 DTD语法规则 DTD定义元素 标
  • CodeForces 1025C Plasticine zebra

    题目大意 题目链接 给定一个由w和b组成的字符串 可以操作任意次 每次操作 0次或多次 可以将字符串分割成左右两个子串 左 右侧子串均前后颠倒 问最终字符串中最多可以有多少个w和b交错 w和b无所谓顺序 题解 构造 比较好想 总述 当最左端
  • Stable-Diffusion-WebUI从代码下载到使用技巧

    一 写在前面 本文用于对AI绘画感兴趣但无计算机编程基础的人 包含本人安装和使用过程中的心得 可供新学者参考 心理准备 电脑性能越高越好 最好有高端显卡 如30系以上 低端显卡也可以 速度和质量感人就是 会要求下载一些软件 模型 涉及环境变
  • 判断一个点是否在圆内(三点确定一个圆)

    三角形的外接圆圆心是任意两边的垂直平分线的交点 三角形外接圆圆心叫外心
  • element的滚动去掉横向_textarea去掉滚动条 textarea横向或纵向滚动条的去掉方法

    在IE下文本框textarea会显示滚动条 如下 去掉textarea横向或纵向滚动条办法 可以通过overflow x hidden和overflow y hidden控制横向和纵向滚动条 要设置textarea文本域的滚动条是否开启 使
  • 30 个 Python 教程和技巧

    如果您让任何 Python 程序员讲述 Python 的优势 他会引用简洁和高可读性作为最有影响力的优势 在本 Python 教程中 我们将介绍许多基本的 Python 教程和技巧 这些技巧和技巧将验证上述两点 自从我开始使用 Python
  • 四年Android面试遇到的问题整理,算法太TM重要了

    何为成长 成长是指自我提升 一方面是本身的个人能力 另一方面是社会对你的认可度 最终 程序员的职位和薪水都能在成长中得以体现 很多人对成长有误解 在他们眼中 随着工作年限的提高 成长是理所当然的事情 这其实是一个误区 两个程序员同时工作3年
  • 解决大批量数据导出Excel产生内存溢出的方案

    相关文章 java操作Excel PDF文件 Java操作Excel之理解JXL 读取Excel Java操作Excel之理解JXL 读取Excel模板动态写入数据并生成Excel 推荐圈子 GT Grid 更多相关推荐 POI或者JXL在