POI Sax 事件驱动解析Excel2007文件
mhr18 2025-01-21 19:36 13 浏览 0 评论
Excel2007版本的代码如下,本文主要是用于POI解析大文件Excel容易出现内存溢出的现象而提出解决方案,故此解决了大数据量的Excel文件解析的难度,在此拿出来贡献给大家,谢谢!
1. Office2007与Office Open XML
在Office 2007之前,Office一直都是以二进制位的方式存储,但这种格式不易被其它软件拿来使用,在各界的压力下,MicroSoft于2005年发布了基于XML的ooxml开放文档标准。ooxml的xml schema强调减少load time,增快parsing speed,将child elements分开存储,而不是multiple attributes一起存,这有点类似于HTML的结构。ooxml 使用XML和ZIP技术结合进行文件存储,因为XML是一个基于文本的格式,而且ZIP容器支持内容的压缩,所以其一大优势就是可以大大减小文件的尺寸。其它特点这里不再叙述。
2. SAX方式解析XML
SAX全称Simple API for XML,它是一个接口,也是一个软件包。它是一种XML解析的替代方法,不同于DOM解析XML文档时把所有内容一次性加载到内存中的方式,它逐行扫描文档,一边扫描,一边解析。所以那些只需要单遍读取内容的应用程序就可以从SAX解析中受益,这对大型文档的解析是个巨大优势。另外,SAX “推" 模型可用于广播环境,能够同时注册多个ContentHandler,并行接收事件,而不是在一个管道中一个接一个地进行处理。一些支持 SAX 的语法分析器包括 Xerces,Apache parser(以前的 IBM 语法分析器)、MSXML(Microsoft 语法分析器)和 XDK(Oracle 语法分析器)。这些语法分析器是最灵活的,因为它们还支持 DOM。
3. POI以SAX解析excel2007文件
所需jar包:poi-3.10-FINAL-20140208.jar,poi-ooxml-3.10-FINAL-20140208.jar, poi-ooxml-schemas-3.10-FINAL-20140208.jar
xercesImpl.jar xml-apis-2.0.2.jar xmlbeans-2.6.0.jar sax2.jar
package com.boguan.bte.util.excel; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.apache.poi.openxml4j.exceptions.OpenXML4JException; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.ss.usermodel.BuiltinFormats; import org.apache.poi.ss.usermodel.DataFormatter; import org.apache.poi.xssf.eventusermodel.XSSFReader; import org.apache.poi.xssf.model.SharedStringsTable; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.helpers.XMLReaderFactory; import com.boguan.bte.service.common.IExcelRowReader; import com.boguan.bte.service.common.impl.ExcelRowReader; /** * 名称: ExcelXlsxReader.java<br> * 描述: <br> * 类型: JAVA<br> * 最近修改时间:2016年7月5日 上午10:00:52<br> * * @since 2016年7月5日 * @author “” */ public class ExcelXlsxReader extends DefaultHandler { private IExcelRowReader rowReader; public void setRowReader(IExcelRowReader rowReader) { this.rowReader = rowReader; } /** * 共享字符串表 */ private SharedStringsTable sst; /** * 上一次的内容 */ private String lastContents; /** * 字符串标识 */ private boolean nextIsString; /** * 工作表索引 */ private int sheetIndex = -1; /** * 行集合 */ private List<String> rowlist = new ArrayList<String>; /** * 当前行 */ private int curRow = 0; /** * 当前列 */ private int curCol = 0; /** * T元素标识 */ private boolean isTElement; /** * 异常信息,如果为空则表示没有异常 */ private String exceptionMessage; /** * 单元格数据类型,默认为字符串类型 */ private CellDataType nextDataType = CellDataType.SSTINDEX; private final DataFormatter formatter = new DataFormatter; private short formatIndex; private String formatString; // 定义前一个元素和当前元素的位置,用来计算其中空的单元格数量,如A6和A8等 private String preRef = null, ref = null; // 定义该文档一行最大的单元格数,用来补全一行最后可能缺失的单元格 private String maxRef = null; /** * 单元格 */ private StylesTable stylesTable; /** * 遍历工作簿中所有的电子表格 * * @param filename * @throws IOException * @throws OpenXML4JException * @throws SAXException * @throws Exception */ public void process(String filename) throws IOException, OpenXML4JException, SAXException { OPCPackage pkg = OPCPackage.open(filename); XSSFReader xssfReader = new XSSFReader(pkg); stylesTable = xssfReader.getStylesTable; SharedStringsTable sst = xssfReader.getSharedStringsTable; XMLReader parser = this.fetchSheetParser(sst); Iterator<InputStream> sheets = xssfReader.getSheetsData; while (sheets.hasNext) { curRow = 0; sheetIndex++; InputStream sheet = sheets.next; InputSource sheetSource = new InputSource(sheet); parser.parse(sheetSource); sheet.close; } } public XMLReader fetchSheetParser(SharedStringsTable sst) throws SAXException { XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser"); this.sst = sst; parser.setContentHandler(this); return parser; } public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { // c => 单元格 if ("c".equals(name)) { // 前一个单元格的位置 if (preRef == null) { preRef = attributes.getValue("r"); } else { preRef = ref; } // 当前单元格的位置 ref = attributes.getValue("r"); // 设定单元格类型 this.setNextDataType(attributes); // Figure out if the value is an index in the SST String cellType = attributes.getValue("t"); if (cellType != null && cellType.equals("s")) { nextIsString = true; } else { nextIsString = false; } } // 当元素为t时 if ("t".equals(name)) { isTElement = true; } else { isTElement = false; } // 置空 lastContents = ""; } /** * 单元格中的数据可能的数据类型 */ enum CellDataType { BOOL, ERROR, FORMULA, INLINESTR, SSTINDEX, NUMBER, DATE, NULL } /** * 处理数据类型 * * @param attributes */ public void setNextDataType(Attributes attributes) { nextDataType = CellDataType.NUMBER; formatIndex = -1; formatString = null; String cellType = attributes.getValue("t"); String cellStyleStr = attributes.getValue("s"); String columData = attributes.getValue("r"); if ("b".equals(cellType)) { nextDataType = CellDataType.BOOL; } else if ("e".equals(cellType)) { nextDataType = CellDataType.ERROR; } else if ("inlineStr".equals(cellType)) { nextDataType = CellDataType.INLINESTR; } else if ("s".equals(cellType)) { nextDataType = CellDataType.SSTINDEX; } else if ("str".equals(cellType)) { nextDataType = CellDataType.FORMULA; } if (cellStyleStr != null) { int styleIndex = Integer.parseInt(cellStyleStr); XSSFCellStyle style = stylesTable.getStyleAt(styleIndex); formatIndex = style.getDataFormat; formatString = style.getDataFormatString; if ("m/d/yy" == formatString) { nextDataType = CellDataType.DATE; formatString = "yyyy-MM-dd hh:mm:ss.SSS"; } if (formatString == null) { nextDataType = CellDataType.NULL; formatString = BuiltinFormats.getBuiltinFormat(formatIndex); } } } /** * 对解析出来的数据进行类型处理 * * @param value * 单元格的值(这时候是一串数字) * @param thisStr * 一个空字符串 * @return */ @SuppressWarnings("deprecation") public String getDataValue(String value, String thisStr) { switch (nextDataType) { // 这几个的顺序不能随便交换,交换了很可能会导致数据错误 case BOOL: char first = value.charAt(0); thisStr = first == '0' ? "FALSE" : "TRUE"; break; case ERROR: thisStr = "\"ERROR:" + value.toString + '"'; break; case FORMULA: thisStr = '"' + value.toString + '"'; break; case INLINESTR: XSSFRichTextString rtsi = new XSSFRichTextString(value.toString); thisStr = rtsi.toString; rtsi = null; break; case SSTINDEX: String sstIndex = value.toString; try { int idx = Integer.parseInt(sstIndex); XSSFRichTextString rtss = new XSSFRichTextString(sst.getEntryAt(idx)); thisStr = rtss.toString; rtss = null; } catch (NumberFormatException ex) { thisStr = value.toString; } break; case NUMBER: if (formatString != null) { thisStr = formatter.formatRawCellContents(Double.parseDouble(value), formatIndex, formatString).trim; } else { thisStr = value; } thisStr = thisStr.replace("_", "").trim; break; case DATE: thisStr = formatter.formatRawCellContents(Double.parseDouble(value), formatIndex, formatString); // 对日期字符串作特殊处理 thisStr = thisStr.replace(" ", "T"); break; default: thisStr = " "; break; } return thisStr; } @Override public void endElement(String uri, String localName, String name) throws SAXException { // 根据SST的索引值的到单元格的真正要存储的字符串 // 这时characters方法可能会被调用多次 if (nextIsString) { int idx = Integer.parseInt(lastContents); lastContents = new XSSFRichTextString(sst.getEntryAt(idx)).toString; } // t元素也包含字符串 if (isTElement) { // 将单元格内容加入rowlist中,在这之前先去掉字符串前后的空白符 String value = lastContents.trim; rowlist.add(curCol, value); curCol++; isTElement = false; } else if ("v".equals(name)) { // v => 单元格的值,如果单元格是字符串则v标签的值为该字符串在SST中的索引 String value = this.getDataValue(lastContents.trim, ""); // 补全单元格之间的空单元格 if (!ref.equals(preRef)) { int len = countNullCell(ref, preRef); for (int i = 0; i < len; i++) { rowlist.add(curCol, ""); curCol++; } } rowlist.add(curCol, value); curCol++; } else { // 如果标签名称为 row ,这说明已到行尾,调用 optRows 方法 if (name.equals("row")) { // 默认第一行为表头,以该行单元格数目为最大数目 if (curRow == 0) { maxRef = ref; } // 补全一行尾部可能缺失的单元格 if (maxRef != null) { int len = countNullCell(maxRef, ref); for (int i = 0; i <= len; i++) { rowlist.add(curCol, ""); curCol++; } } rowReader.getRows(sheetIndex, curRow, rowlist); rowlist.clear; curRow++; curCol = 0; preRef = null; ref = null; } } } /** * 计算两个单元格之间的单元格数目(同一行) * * @param ref * @param preRef * @return */ public int countNullCell(String ref, String preRef) { // excel2007最大行数是1048576,最大列数是16384,最后一列列名是XFD String xfd = ref.replaceAll("\\d+", ""); String xfd_1 = preRef.replaceAll("\\d+", ""); xfd = fillChar(xfd, 3, '@', true); xfd_1 = fillChar(xfd_1, 3, '@', true); char letter = xfd.toCharArray; char letter_1 = xfd_1.toCharArray; int res = (letter[0] - letter_1[0]) * 26 * 26 + (letter[1] - letter_1[1]) * 26 + (letter[2] - letter_1[2]); return res - 1; } /** * 字符串的填充 * * @param str * @param len * @param let * @param isPre * @return */ String fillChar(String str, int len, char let, boolean isPre) { int len_1 = str.length; if (len_1 < len) { if (isPre) { for (int i = 0; i < (len - len_1); i++) { str = let + str; } } else { for (int i = 0; i < (len - len_1); i++) { str = str + let; } } } return str; } @Override public void characters(char[] ch, int start, int length) throws SAXException { // 得到单元格内容的值 lastContents += new String(ch, start, length); } /** * @return the exceptionMessage */ public String getExceptionMessage { return exceptionMessage; } public static void main(String[] args) { IExcelRowReader rowReader = new ExcelRowReader; try { // ExcelReaderUtil.readExcel(rowReader, // "E://2016-07-04-011940a.xls"); System.out.println("**********************************************"); ExcelReaderUtil.readExcel(rowReader, "E://test.xlsx"); } catch (Exception e) { e.printStackTrace; } } }
相关推荐
- 如何检查 Linux 服务器是物理服务器还是虚拟服务器?
-
在企业级运维、故障排查和性能调优过程中,准确了解服务器的运行环境至关重要。无论是物理机还是虚拟机,都存在各自的优势与限制。在很多场景下,尤其是当你继承一台服务器而不清楚底层硬件细节时,如何快速辨识它是...
- 第四节 Windows 系统 Docker 安装全指南
-
一、Docker在Windows上的运行原理(一)架构限制说明Docker本质上依赖Linux内核特性(如Namespaces、Cgroups等),因此在Windows系统上无法直...
- C++ std:shared_ptr自定义allocator引入内存池
-
当C++项目里做了大量的动态内存分配与释放,可能会导致内存碎片,使系统性能降低。当动态内存分配的开销变得不容忽视时,一种解决办法是一次从操作系统分配一块大的静态内存作为内存池进行手动管理,堆对象内存分...
- Activiti 8.0.0 发布,业务流程管理与工作流系统
-
Activiti8.0.0现已发布。Activiti是一个业务流程管理(BPM)和工作流系统,适用于开发人员和系统管理员。其核心是超快速、稳定的BPMN2流程引擎。Activiti可以...
- MyBatis动态SQL的5种高级玩法,90%的人只用过3种
-
MyBatis动态SQL在日常开发中频繁使用,但大多数开发者仅掌握基础标签。本文将介绍五种高阶技巧,助你解锁更灵活的SQL控制能力。一、智能修剪(Trim标签)应用场景:动态处理字段更新,替代<...
- Springboot数据访问(整合Mybatis Plus)
-
Springboot整合MybatisPlus1、创建数据表2、引入maven依赖mybatis-plus-boot-starter主要引入这个依赖,其他相关的依赖在这里就不写了。3、项目结构目录h...
- 盘点金州勇士在奥克兰13年的13大球星 满满的全是...
-
见证了两个月前勇士与猛龙那个史诗般的系列赛后,甲骨文球馆正式成为了历史。那个大大的红色标志被一个字母一个字母地移除,在周四,一切都成为了过去式。然而这座,别名为“Roaracle”(译注:Roar怒吼...
- Mybatis入门看这一篇就够了(mybatis快速入门)
-
什么是MyBatisMyBatis本是apache的一个开源项目iBatis,2010年这个项目由apachesoftwarefoundation迁移到了googlecode,并且改名为M...
- Springboot数据访问(整合druid数据源)
-
Springboot整合druid数据源基本概念SpringBoot默认的数据源是:2.0之前:org.apache.tomcat.jdbc.pool.DataSource2.0及之后:com.z...
- Linux 中的 "/etc/profile.d" 目录有什么作用 ?
-
什么是/etc/profile.d/目录?/etc/profile.d/目录是Linux系统不可或缺的一部分保留配置脚本。它与/etc/profile文件相关联,这是一个启动脚本,该脚...
- 企业数据库安全管理规范(企业数据库安全管理规范最新版)
-
1.目的为规范数据库系统安全使用活动,降低因使用不当而带来的安全风险,保障数据库系统及相关应用系统的安全,特制定本数据库安全管理规范。2.适用范围本规范中所定义的数据管理内容,特指存放在信息系统数据库...
- Oracle 伪列!这些隐藏用法你都知道吗?
-
在Oracle数据库中,有几位特殊的“成员”——伪列,它们虽然不是表中真实存在的物理列,但却能在数据查询、处理过程中发挥出意想不到的强大作用。今天给大家分享Oracle伪列的使用技巧,无论...
- Oracle 高效处理数据的隐藏神器:临时表妙用
-
各位数据库搬砖人,在Oracle的代码世界里闯荡,处理复杂业务时,是不是总被数据“搅得头大”?今天给大家安利一个超实用的隐藏神器——临时表!当你需要临时存储中间计算结果,又不想污染正式数据表...
- Oracle 数据库查询:多表查询(oracle多表关联查询)
-
一、多表查询基础1.JOIN操作-INNERJOIN:返回两个表中满足连接条件的匹配行,不保留未匹配数据。SELECTa.emp_id,b.dept_nameFROMempl...
- 一文掌握怎么利用Shell+Python实现多数据源的异地备份程序
-
简介:在信息化时代,数据安全和业务连续性已成为企业和个人用户关注的焦点。无论是网站数据、数据库、日志文件,还是用户上传的文档、图片等,数据一旦丢失,损失难以估量。尤其是当数据分布在多个不同的目录、服务...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 如何检查 Linux 服务器是物理服务器还是虚拟服务器?
- 第四节 Windows 系统 Docker 安装全指南
- C++ std:shared_ptr自定义allocator引入内存池
- Activiti 8.0.0 发布,业务流程管理与工作流系统
- MyBatis动态SQL的5种高级玩法,90%的人只用过3种
- Springboot数据访问(整合Mybatis Plus)
- 盘点金州勇士在奥克兰13年的13大球星 满满的全是...
- Mybatis入门看这一篇就够了(mybatis快速入门)
- Springboot数据访问(整合druid数据源)
- Linux 中的 "/etc/profile.d" 目录有什么作用 ?
- 标签列表
-
- oracle位图索引 (74)
- oracle基目录 (50)
- oracle批量插入数据 (65)
- oracle事务隔离级别 (53)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)