https://blog.csdn.net/lishengbo/article/details/40711769

Office软件一直是一个诲誉参半的软件,广大普通计算机用户用Office来满足日常办公需求,于是就产生了很多生产数据和文档,需要和企业单位的专用办公系统对接,而Office的解析工作一直是程序员非常头痛的问题,经常招致程序员的谩骂,也被誉为是微软最烂的发明之一。POI的诞生解决了Excel的解析难题(POI即“讨厌的电子表格”,确实很讨厌,我也很讨厌Excel),但如果用不好POI,也会导致程序出现一些BUG,例如内存溢出,假空行,公式等等问题。下面介绍一种解决POI读取Excel内存溢出的问题。

POI读取Excel有两种模式,一种是用户模式,一种是SAX模式,将xlsx格式的文档转换成CVS格式后再进行处理用户模式相信大家都很清楚,也是POI常用的方式,用户模式API接口丰富,我们可以很容易的使用POI的API读取Excel,但用户模式消耗的内存很大,当遇到很多sheet、大数据网格、假空行、公式等问题时,很容易导致内存溢出。POI官方推荐解决内存溢出的方式使用CVS格式解析,我们不可能手工将Excel文件转换成CVS格式再上传,这样做太麻烦了,好再POI给出了xlsx转换CVS的例子,基于这个例子我进行了一下改造,即可解决用户模式读取Excel内存溢出的问题。下面附上代码:

[java] view plain copy
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements.  See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License.  You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. import java.io.IOException;
  16. import java.io.InputStream;
  17. import java.io.PrintStream;
  18. import java.text.SimpleDateFormat;
  19. import java.util.ArrayList;
  20. import java.util.Date;
  21. import java.util.List;
  22. import javax.xml.parsers.ParserConfigurationException;
  23. import javax.xml.parsers.SAXParser;
  24. import javax.xml.parsers.SAXParserFactory;
  25. import org.apache.poi.hssf.usermodel.HSSFDateUtil;
  26. import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
  27. import org.apache.poi.openxml4j.opc.OPCPackage;
  28. import org.apache.poi.openxml4j.opc.PackageAccess;
  29. import org.apache.poi.ss.usermodel.BuiltinFormats;
  30. import org.apache.poi.ss.usermodel.DataFormatter;
  31. import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable;
  32. import org.apache.poi.xssf.eventusermodel.XSSFReader;
  33. import org.apache.poi.xssf.model.StylesTable;
  34. import org.apache.poi.xssf.usermodel.XSSFCellStyle;
  35. import org.apache.poi.xssf.usermodel.XSSFRichTextString;
  36. import org.xml.sax.Attributes;
  37. import org.xml.sax.InputSource;
  38. import org.xml.sax.SAXException;
  39. import org.xml.sax.XMLReader;
  40. import org.xml.sax.helpers.DefaultHandler;
  41. /**
  42. * 使用CVS模式解决XLSX文件,可以有效解决用户模式内存溢出的问题
  43. * 该模式是POI官方推荐的读取大数据的模式,在用户模式下,数据量较大、Sheet较多、或者是有很多无用的空行的情况
  44. * ,容易出现内存溢出,用户模式读取Excel的典型代码如下: FileInputStream file=new
  45. * FileInputStream("c:\\test.xlsx"); Workbook wb=new XSSFWorkbook(file);
  46. *
  47. *
  48. * @author 山人
  49. */
  50. public class XLSXCovertCSVReader {
  51. /**
  52. * The type of the data value is indicated by an attribute on the cell. The
  53. * value is usually in a "v" element within the cell.
  54. */
  55. enum xssfDataType {
  56. BOOL, ERROR, FORMULA, INLINESTR, SSTINDEX, NUMBER,
  57. }
  58. /**
  59. * 使用xssf_sax_API处理Excel,请参考: http://poi.apache.org/spreadsheet/how-to.html#xssf_sax_api
  60. * <p/>
  61. * Also see Standard ECMA-376, 1st edition, part 4, pages 1928ff, at
  62. * http://www.ecma-international.org/publications/standards/Ecma-376.htm
  63. * <p/>
  64. * A web-friendly version is http://openiso.org/Ecma/376/Part4
  65. */
  66. class MyXSSFSheetHandler extends DefaultHandler {
  67. /**
  68. * Table with styles
  69. */
  70. private StylesTable stylesTable;
  71. /**
  72. * Table with unique strings
  73. */
  74. private ReadOnlySharedStringsTable sharedStringsTable;
  75. /**
  76. * Destination for data
  77. */
  78. private final PrintStream output;
  79. /**
  80. * Number of columns to read starting with leftmost
  81. */
  82. private final int minColumnCount;
  83. // Set when V start element is seen
  84. private boolean vIsOpen;
  85. // Set when cell start element is seen;
  86. // used when cell close element is seen.
  87. private xssfDataType nextDataType;
  88. // Used to format numeric cell values.
  89. private short formatIndex;
  90. private String formatString;
  91. private final DataFormatter formatter;
  92. private int thisColumn = -1;
  93. // The last column printed to the output stream
  94. private int lastColumnNumber = -1;
  95. // Gathers characters as they are seen.
  96. private StringBuffer value;
  97. private String[] record;
  98. private List<String[]> rows = new ArrayList<String[]>();
  99. private boolean isCellNull = false;
  100. /**
  101. * Accepts objects needed while parsing.
  102. *
  103. * @param styles
  104. *            Table of styles
  105. * @param strings
  106. *            Table of shared strings
  107. * @param cols
  108. *            Minimum number of columns to show
  109. * @param target
  110. *            Sink for output
  111. */
  112. public MyXSSFSheetHandler(StylesTable styles,
  113. ReadOnlySharedStringsTable strings, int cols, PrintStream target) {
  114. this.stylesTable = styles;
  115. this.sharedStringsTable = strings;
  116. this.minColumnCount = cols;
  117. this.output = target;
  118. this.value = new StringBuffer();
  119. this.nextDataType = xssfDataType.NUMBER;
  120. this.formatter = new DataFormatter();
  121. record = new String[this.minColumnCount];
  122. rows.clear();// 每次读取都清空行集合
  123. }
  124. /*
  125. * (non-Javadoc)
  126. *
  127. * @see
  128. * org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String,
  129. * java.lang.String, java.lang.String, org.xml.sax.Attributes)
  130. */
  131. public void startElement(String uri, String localName, String name,
  132. Attributes attributes) throws SAXException {
  133. if ("inlineStr".equals(name) || "v".equals(name)) {
  134. vIsOpen = true;
  135. // Clear contents cache
  136. value.setLength(0);
  137. }
  138. // c => cell
  139. else if ("c".equals(name)) {
  140. // Get the cell reference
  141. String r = attributes.getValue("r");
  142. int firstDigit = -1;
  143. for (int c = 0; c < r.length(); ++c) {
  144. if (Character.isDigit(r.charAt(c))) {
  145. firstDigit = c;
  146. break;
  147. }
  148. }
  149. thisColumn = nameToColumn(r.substring(0, firstDigit));
  150. // Set up defaults.
  151. this.nextDataType = xssfDataType.NUMBER;
  152. this.formatIndex = -1;
  153. this.formatString = null;
  154. String cellType = attributes.getValue("t");
  155. String cellStyleStr = attributes.getValue("s");
  156. if ("b".equals(cellType))
  157. nextDataType = xssfDataType.BOOL;
  158. else if ("e".equals(cellType))
  159. nextDataType = xssfDataType.ERROR;
  160. else if ("inlineStr".equals(cellType))
  161. nextDataType = xssfDataType.INLINESTR;
  162. else if ("s".equals(cellType))
  163. nextDataType = xssfDataType.SSTINDEX;
  164. else if ("str".equals(cellType))
  165. nextDataType = xssfDataType.FORMULA;
  166. else if (cellStyleStr != null) {
  167. // It's a number, but almost certainly one
  168. // with a special style or format
  169. int styleIndex = Integer.parseInt(cellStyleStr);
  170. XSSFCellStyle style = stylesTable.getStyleAt(styleIndex);
  171. this.formatIndex = style.getDataFormat();
  172. this.formatString = style.getDataFormatString();
  173. if (this.formatString == null)
  174. this.formatString = BuiltinFormats
  175. .getBuiltinFormat(this.formatIndex);
  176. }
  177. }
  178. }
  179. /*
  180. * (non-Javadoc)
  181. *
  182. * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String,
  183. * java.lang.String, java.lang.String)
  184. */
  185. public void endElement(String uri, String localName, String name)
  186. throws SAXException {
  187. String thisStr = null;
  188. // v => contents of a cell
  189. if ("v".equals(name)) {
  190. // Process the value contents as required.
  191. // Do now, as characters() may be called more than once
  192. switch (nextDataType) {
  193. case BOOL:
  194. char first = value.charAt(0);
  195. thisStr = first == '0' ? "FALSE" : "TRUE";
  196. break;
  197. case ERROR:
  198. thisStr = "\"ERROR:" + value.toString() + '"';
  199. break;
  200. case FORMULA:
  201. // A formula could result in a string value,
  202. // so always add double-quote characters.
  203. thisStr = '"' + value.toString() + '"';
  204. break;
  205. case INLINESTR:
  206. // TODO: have seen an example of this, so it's untested.
  207. XSSFRichTextString rtsi = new XSSFRichTextString(
  208. value.toString());
  209. thisStr = '"' + rtsi.toString() + '"';
  210. break;
  211. case SSTINDEX:
  212. String sstIndex = value.toString();
  213. try {
  214. int idx = Integer.parseInt(sstIndex);
  215. XSSFRichTextString rtss = new XSSFRichTextString(
  216. sharedStringsTable.getEntryAt(idx));
  217. thisStr = '"' + rtss.toString() + '"';
  218. } catch (NumberFormatException ex) {
  219. output.println("Failed to parse SST index '" + sstIndex
  220. + "': " + ex.toString());
  221. }
  222. break;
  223. case NUMBER:
  224. String n = value.toString();
  225. // 判断是否是日期格式
  226. if (HSSFDateUtil.isADateFormat(this.formatIndex, n)) {
  227. Double d = Double.parseDouble(n);
  228. Date date=HSSFDateUtil.getJavaDate(d);
  229. thisStr=formateDateToString(date);
  230. } else if (this.formatString != null)
  231. thisStr = formatter.formatRawCellContents(
  232. Double.parseDouble(n), this.formatIndex,
  233. this.formatString);
  234. else
  235. thisStr = n;
  236. break;
  237. default:
  238. thisStr = "(TODO: Unexpected type: " + nextDataType + ")";
  239. break;
  240. }
  241. // Output after we've seen the string contents
  242. // Emit commas for any fields that were missing on this row
  243. if (lastColumnNumber == -1) {
  244. lastColumnNumber = 0;
  245. }
  246. //判断单元格的值是否为空
  247. if (thisStr == null || "".equals(isCellNull)) {
  248. isCellNull = true;// 设置单元格是否为空值
  249. }
  250. record[thisColumn] = thisStr;
  251. // Update column
  252. if (thisColumn > -1)
  253. lastColumnNumber = thisColumn;
  254. } else if ("row".equals(name)) {
  255. // Print out any missing commas if needed
  256. if (minColumns > 0) {
  257. // Columns are 0 based
  258. if (lastColumnNumber == -1) {
  259. lastColumnNumber = 0;
  260. }
  261. if (isCellNull == false && record[0] != null
  262. && record[1] != null)// 判断是否空行
  263. {
  264. rows.add(record.clone());
  265. isCellNull = false;
  266. for (int i = 0; i < record.length; i++) {
  267. record[i] = null;
  268. }
  269. }
  270. }
  271. lastColumnNumber = -1;
  272. }
  273. }
  274. public List<String[]> getRows() {
  275. return rows;
  276. }
  277. public void setRows(List<String[]> rows) {
  278. this.rows = rows;
  279. }
  280. /**
  281. * Captures characters only if a suitable element is open. Originally
  282. * was just "v"; extended for inlineStr also.
  283. */
  284. public void characters(char[] ch, int start, int length)
  285. throws SAXException {
  286. if (vIsOpen)
  287. value.append(ch, start, length);
  288. }
  289. /**
  290. * Converts an Excel column name like "C" to a zero-based index.
  291. *
  292. * @param name
  293. * @return Index corresponding to the specified name
  294. */
  295. private int nameToColumn(String name) {
  296. int column = -1;
  297. for (int i = 0; i < name.length(); ++i) {
  298. int c = name.charAt(i);
  299. column = (column + 1) * 26 + c - 'A';
  300. }
  301. return column;
  302. }
  303. private String formateDateToString(Date date) {
  304. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//格式化日期
  305. return sdf.format(date);
  306. }
  307. }
  308. // /
  309. private OPCPackage xlsxPackage;
  310. private int minColumns;
  311. private PrintStream output;
  312. private String sheetName;
  313. /**
  314. * Creates a new XLSX -> CSV converter
  315. *
  316. * @param pkg
  317. *            The XLSX package to process
  318. * @param output
  319. *            The PrintStream to output the CSV to
  320. * @param minColumns
  321. *            The minimum number of columns to output, or -1 for no minimum
  322. */
  323. public XLSXCovertCSVReader(OPCPackage pkg, PrintStream output,
  324. String sheetName, int minColumns) {
  325. this.xlsxPackage = pkg;
  326. this.output = output;
  327. this.minColumns = minColumns;
  328. this.sheetName = sheetName;
  329. }
  330. /**
  331. * Parses and shows the content of one sheet using the specified styles and
  332. * shared-strings tables.
  333. *
  334. * @param styles
  335. * @param strings
  336. * @param sheetInputStream
  337. */
  338. public List<String[]> processSheet(StylesTable styles,
  339. ReadOnlySharedStringsTable strings, InputStream sheetInputStream)
  340. throws IOException, ParserConfigurationException, SAXException {
  341. InputSource sheetSource = new InputSource(sheetInputStream);
  342. SAXParserFactory saxFactory = SAXParserFactory.newInstance();
  343. SAXParser saxParser = saxFactory.newSAXParser();
  344. XMLReader sheetParser = saxParser.getXMLReader();
  345. MyXSSFSheetHandler handler = new MyXSSFSheetHandler(styles, strings,
  346. this.minColumns, this.output);
  347. sheetParser.setContentHandler(handler);
  348. sheetParser.parse(sheetSource);
  349. return handler.getRows();
  350. }
  351. /**
  352. * 初始化这个处理程序 将
  353. *
  354. * @throws IOException
  355. * @throws OpenXML4JException
  356. * @throws ParserConfigurationException
  357. * @throws SAXException
  358. */
  359. public List<String[]> process() throws IOException, OpenXML4JException,
  360. ParserConfigurationException, SAXException {
  361. ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(
  362. this.xlsxPackage);
  363. XSSFReader xssfReader = new XSSFReader(this.xlsxPackage);
  364. List<String[]> list = null;
  365. StylesTable styles = xssfReader.getStylesTable();
  366. XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader
  367. .getSheetsData();
  368. int index = 0;
  369. while (iter.hasNext()) {
  370. InputStream stream = iter.next();
  371. String sheetNameTemp = iter.getSheetName();
  372. if (this.sheetName.equals(sheetNameTemp)) {
  373. list = processSheet(styles, strings, stream);
  374. stream.close();
  375. ++index;
  376. }
  377. }
  378. return list;
  379. }
  380. /**
  381. * 读取Excel
  382. *
  383. * @param path
  384. *            文件路径
  385. * @param sheetName
  386. *            sheet名称
  387. * @param minColumns
  388. *            列总数
  389. * @return
  390. * @throws SAXException
  391. * @throws ParserConfigurationException
  392. * @throws OpenXML4JException
  393. * @throws IOException
  394. */
  395. private static List<String[]> readerExcel(String path, String sheetName,
  396. int minColumns) throws IOException, OpenXML4JException,
  397. ParserConfigurationException, SAXException {
  398. OPCPackage p = OPCPackage.open(path, PackageAccess.READ);
  399. XLSXCovertCSVReader xlsx2csv = new XLSXCovertCSVReader(p, System.out,
  400. sheetName, minColumns);
  401. List<String[]> list = xlsx2csv.process();
  402. p.close();
  403. return list;
  404. }
  405. public static void main(String[] args) throws Exception {
  406. List<String[]> list = XLSXCovertCSVReader
  407. .readerExcel(
  408. "F:\\test.xlsx",
  409. "Sheet1", 17);
  410. for (String[] record : list) {
  411. for (String cell : record) {
  412. System.out.print(cell + "  ");
  413. }
  414. System.out.println();
  415. }
  416. }
  417. }

解决POI读取Excel内存溢出的问题相关推荐

  1. 解决POI读取Excel百万级内存溢出问题

    使用传统poi来操作大数据量的excel会出现内存溢出的问题,根据各种资源,亲试了一个可用工具类,附代码如下: 一.基于eventusermodel的excel解析工具类 package com.ta ...

  2. java字符串换行符替换成段落标记_导出EXCEL换行符br为什么没有起到作用/poi导出excel内存溢出...

    导出EXCEL换行符br为什么没有起到作用 br是html中的换行符,在excel中并不起效,excel中换行是用alt enter强制插入的,或者用vba代码强制换行的. 导出EXCEL换行符为什么 ...

  3. POI解决读入Excel内存溢出

    POI读取excel表 下面是结构图 大批量数据读取的问题 在项目中遇到二十万行数据的excel用poi读取会内存溢出,一般方法是调大tomcat的内存,但是调到2048M还是会内存溢出报错 poi官 ...

  4. poi读取Excel日期为数字的解决方法

    这个问题虽然也比较常见,解决办法也比较简单,但是网上有一些代码不全,思路混乱,乱七八糟的办法,容易误导大家,特地来为大家开路 这里分享一下我的一个思路 Maven依赖 <!--POI--> ...

  5. Java 中如何解决 POI 读写 excel 几万行数据时内存溢出的问题?(附源码)

    >>号外:关注"Java精选"公众号,菜单栏->聚合->干货分享,回复关键词领取视频资料.开源项目. 1. Excel2003与Excel2007 两个版本 ...

  6. 记一次解决poi XSSFWorkbook导出excel内存溢出换成SXSSFWorkbook还是内存溢出的问题

    1.需求背景 工作中同事遇到一个导出excel内存溢出的问题,让我帮忙解决.一个excel大约有800个sheet页.原写法使用的XSSFWorkbook写入导出.百度了下了解到应该替换成SXSSFW ...

  7. POI读取Excel模板并导出大量数据

    POI读取Excel模板并导出大量数据 我在使用XSSFWorkbook读取Excel模板并导出大量数据(百万级)时,发现很长时间没有响应,debugger模式发现在读取第三四十万条数据时,程序直接停 ...

  8. java通过poi读取excel中的日期类型数据或自定义类型日期

    java通过poi读取excel中的日期类型数据或自定义类型日期 Java 读取Excel表格日期类型数据的时候,读出来的是这样的  12-十月-2019,而Excel中输入的是 2019/10/12 ...

  9. 使用POI 读取 Excel 文件,读取手机号码 变成 1.3471022771E10

    使用POI 读取 Excel 文件,读取手机号码 变成 1.3471022771E10 [问题点数:40分,结帖人xieyongqiu] 不显示删除回复             显示所有回复     ...

  10. Java教程:使用POI读取excel文档(根据BV1bJ411G7Aw整理)

    Java教程:使用POI读取excel文档(根据BV1bJ411G7Aw整理) 最近公司需要我做一个导出Excel表格的功能,为此来学习一下POI,在这里记录一下学习笔记.B站直接搜BV1bJ411G ...

最新文章

  1. Python使用matplotlib函数subplot可视化多个不同颜色的折线图、使用set_minor_locator函数指定坐标轴次刻度数值倍数(MultipleLocator)
  2. mybatis整合spring下的的各种配置文件
  3. 多种方式读取文件内容
  4. 有感而发,恍然大悟。
  5. 微型计算机启天A5000-B124说明,微型计算机原理及应用知识点总结
  6. JavaScript组件之JQuery(A~Z)教程(基于Asp.net运行环境)[示例代码下载](一)
  7. Spring Boot 2应用程序和OAuth 2 –传统方法
  8. koa2 mysql 中间件_Koa2第二篇:中间件
  9. 《深入理解OSGi:Equinox原理、应用与最佳实践》笔记_1_运行最简单的bundlehelloworld...
  10. 解决Selenium Webdriver执行测试时,每个测试方法都打开一个浏览器窗口的问题
  11. Windows 10 企业版LTSCjihuo
  12. Java 添加Word脚注、尾注
  13. 汉语教学备课工具推荐
  14. android获取机器码,Android平台获取设备SN的说明
  15. 巴比特国际站观察 | 海外新晋“网红”亮相,数字人民币引密码社区热议
  16. 搭档之家:李佳琦“双11”直播最低价,还是贵了?
  17. 融云通讯服务器,发送图片到自己的服务器
  18. 02-StringStringBuilderStringBuffer
  19. 搭建一个点歌QQ机器人,另外还能看美女
  20. windows7计算机窗口介绍,windows7使用技巧详细介绍【图解】

热门文章

  1. Mac上优秀的取色工具——ColoFolXS for mac支持m1
  2. 记一次MacOS 证书信任配置不生效的解决方法(clashX报错:SecTrustSettingsCopyCertificates error: -25262)
  3. 苹果Mac电脑配置flutter开发环境
  4. 怎么在Guitar Pro乐谱中加入哇音
  5. KVO 的代码简洁使用
  6. unity打包IOS填坑1
  7. 美国欲投 2.58 亿美元与中国争夺超算霸主地位
  8. SqlServer 在创建数据库时候指定的初始数据库大小是不能被收缩的
  9. ZOJ3778 Talented Chef(贪心)
  10. Qt:During startup program exited with code 0xc0000135