当前位置:   article > 正文

POI 操作 Excel -大数据量高效读写_poi读取大数据量excel的方法

poi读取大数据量excel的方法

 

前言

poi的读取的三种模式

模式说明读写性
SXSSF内存中保留一定行数数据,超过行数,将索引最低的数据刷入硬盘只写
eventmodel基于事件驱动,SAX的方式解析excel,cup和内存消耗低只读
usermodel传统方式,cpu和内存消耗大可读可写

 

依赖包(3.17版本)

  1. <!-- apache poi 操作Microsoft Document -->
  2. <dependency>
  3. <groupId>org.apache.poi</groupId>
  4. <artifactId>poi</artifactId>
  5. <version>3.17</version>
  6. </dependency>
  7. <!-- Apache POI - Java API To Access Microsoft Format Files -->
  8. <dependency>
  9. <groupId>org.apache.poi</groupId>
  10. <artifactId>poi-ooxml</artifactId>
  11. <version>3.17</version>
  12. </dependency>
  13. <!-- Apache POI - Java API To Access Microsoft Format Files -->
  14. <dependency>
  15. <groupId>org.apache.poi</groupId>
  16. <artifactId>poi-ooxml-schemas</artifactId>
  17. <version>3.17</version>
  18. </dependency>
  19. <!-- poi eventmodel方式 依赖包 -->
  20. <dependency>
  21. <groupId>xerces</groupId>
  22. <artifactId>xercesImpl</artifactId>
  23. <version>2.11.0</version>
  24. </dependency>

 

一、SXSSF (Since POI 3.8 beta3)

 

说明

3.8-beta3以来,POI提供了一个低内存占用的SXSSF API,它构建在XSSF之上。

SXSSF是一个兼容于api的XSSF的流扩展,当需要生成非常大的电子表格时,它将被使用,而堆空间是有限的。SXSSF通过限制对滑动窗口中的行的访问来实现它的低内存占用,而XSSF允许访问文档中的所有行。不再出现在窗口中的较老的行变得不可访问,因为它们被写到磁盘上

在自动刷新模式中,可以指定存取窗口的大小,以便在内存中持有一定数量的行。当达到这个值时,额外一行的创建会导致从存取窗口删除最低索引的行,并将其写到磁盘上。或者,窗口大小可以被设置为动态增长;根据需要,可以通过显式调用flushRows(int keepRows)定期对其进行修剪。

由于实现的流特性,与XSSF相比有以下限制:

  • 只有有限数量的行可以在某个时间点访问。
  • 不支持Sheet.clone()。
  • 不支持公式评估

更多细节

下面的表格对POI的电子表格API的比较特性进行了比较:

示例-写数据到excel

下面的例子写了一张有百行窗口的表格。当行数达到101时,rownum=0的行会被刷新到磁盘,并从内存中删除,当rownum达到102时,          rownum=1的行被刷新

  1. @Test
  2. public void test() throws Exception{
  3. Long start = System.currentTimeMillis();
  4. //内存最大存放100行数据 超过100自动刷新到硬盘中
  5. SXSSFWorkbook wb = new SXSSFWorkbook(100); // keep 100 rows in memory, exceeding rows will be flushed to disk
  6. Sheet sh = wb.createSheet();
  7. for(int rownum = 0; rownum < 500000; rownum++){
  8. Row row = sh.createRow(rownum);//一行
  9. for(int cellnum = 0; cellnum < 10; cellnum++){
  10. Cell cell = row.createCell(cellnum); //一行中一个方格
  11. String address = new CellReference(cell).formatAsString();
  12. cell.setCellValue(address);
  13. }
  14. }
  15. FileOutputStream out = new FileOutputStream("f:/temp/sxssf.xlsx");
  16. wb.write(out);
  17. out.close();
  18. // dispose of temporary files backing this workbook on disk
  19. //处理在磁盘上支持本工作簿的临时文件
  20. wb.dispose();
  21. wb.close();
  22. Long end = System.currentTimeMillis();
  23. System.out.println(end - start + "ms"); //50万条数据写入大概在16秒
  24. }

 

下一个例子关闭了自动刷新(windows size=-1),代码手动控制将数据的部分写入磁盘

 

  1. @Test
  2. public void test3() throws IOException{
  3. Long start = System.currentTimeMillis();
  4. SXSSFWorkbook wb = new SXSSFWorkbook(-1); // turn off auto-flushing and accumulate all rows in memory
  5. Sheet sh = wb.createSheet();
  6. for(int rownum = 0; rownum < 1000; rownum++){
  7. Row row = sh.createRow(rownum);
  8. for(int cellnum = 0; cellnum < 10; cellnum++){
  9. Cell cell = row.createCell(cellnum);
  10. String address = new CellReference(cell).formatAsString();
  11. cell.setCellValue(address);
  12. }
  13. // manually control how rows are flushed to disk
  14. if(rownum % 100 == 0) {
  15. ((SXSSFSheet)sh).flushRows(100); // retain 100 last rows and flush all others
  16. // ((SXSSFSheet)sh).flushRows() is a shortcut for ((SXSSFSheet)sh).flushRows(0),
  17. // this method flushes all rows
  18. }
  19. }
  20. FileOutputStream out = new FileOutputStream("f:/temp/sxssf.xlsx");
  21. wb.write(out);
  22. out.close();
  23. // dispose of temporary files backing this workbook on disk
  24. wb.dispose();
  25. Long end = System.currentTimeMillis();
  26. System.out.println(end - start + "ms"); //100条数据 650ms
  27. }

小结

其核心是减少存储在内存当中的数据,达到一定行数就存储到硬盘的临时文件中

 

二、XSSF and SAX (Event API)

说明

如果内存占用是一个问题,那么对于XSSF来说,您可以获得底层XML数据,并自己处理它。

要使用这个API,您可以构建一个org.apache.poi.xssf.eventmodel.xssfreader的实例。这将在共享字符串表和样式上提供一个不错的接口。它提供了从文件的其余部分获取原始xml数据的方法,然后您将把这些数据传递给SAX。

背景

 

Excel2003与Excel2007

两个版本的最大行数和列数不同,2003版最大行数是65536行,最大列数是256列,2007版及以后的版本最大行数是1048576行,最大列数是16384列。

excel2003是以二进制的方式存储,这种格式不易被其他软件读取使用;而excel2007采用了基于XML的ooxml开放文档标准,ooxml使用XML和ZIP技术结合进行文件存储,XML是一个基于文本的格式,而且ZIP容器支持内容的压缩,所以其一大优势是可以大大减小文件的尺寸。

把xlsx后缀改为zip,打开文件发现目录

打开xl

sharedStrings.xml  共享字符串

styles.xml  excel的样式数据

workbooks.xml excel的sheet

示例

  1. package com.java.poi;
  2. import java.io.InputStream;
  3. import java.util.ArrayList;
  4. import java.util.Iterator;
  5. import java.util.LinkedHashMap;
  6. import java.util.List;
  7. import java.util.Map;
  8. import javax.xml.parsers.ParserConfigurationException;
  9. import org.apache.poi.openxml4j.opc.OPCPackage;
  10. import org.apache.poi.openxml4j.opc.PackageAccess;
  11. import org.apache.poi.util.SAXHelper;
  12. import org.apache.poi.xssf.eventusermodel.XSSFReader;
  13. import org.apache.poi.xssf.model.SharedStringsTable;
  14. import org.apache.poi.xssf.usermodel.XSSFRichTextString;
  15. import org.xml.sax.Attributes;
  16. import org.xml.sax.ContentHandler;
  17. import org.xml.sax.InputSource;
  18. import org.xml.sax.SAXException;
  19. import org.xml.sax.XMLReader;
  20. import org.xml.sax.helpers.DefaultHandler;
  21. import org.xml.sax.helpers.XMLReaderFactory;
  22. import com.mysql.jdbc.util.LRUCache;
  23. public class ExampleEventUserModel {
  24. public void processFirstSheet(String filename) throws Exception {
  25. try(OPCPackage pkg = OPCPackage.open(filename,PackageAccess.READ);){
  26. XSSFReader r = new XSSFReader( pkg );
  27. SharedStringsTable sst = r.getSharedStringsTable();
  28. XMLReader parser = fetchSheetParser(sst);
  29. //process the first sheet
  30. try(InputStream sheet = r.getSheetsData().next()){
  31. InputSource sheetSource = new InputSource(sheet);
  32. parser.parse(sheetSource);
  33. }
  34. }
  35. }
  36. public void processAllSheets(String filename) throws Exception {
  37. try (OPCPackage pkg = OPCPackage.open(filename, PackageAccess.READ)) {
  38. XSSFReader r = new XSSFReader(pkg);
  39. SharedStringsTable sst = r.getSharedStringsTable();
  40. XMLReader parser = fetchSheetParser(sst);
  41. Iterator<InputStream> sheets = r.getSheetsData();
  42. while (sheets.hasNext()) {
  43. System.out.println("Processing new sheet:\n");
  44. try (InputStream sheet = sheets.next()) {
  45. InputSource sheetSource = new InputSource(sheet);
  46. parser.parse(sheetSource);
  47. }
  48. System.out.println("");
  49. }
  50. }
  51. }
  52. public XMLReader fetchSheetParser(SharedStringsTable sst) throws SAXException, ParserConfigurationException {
  53. XMLReader parser =SAXHelper.newXMLReader();
  54. ContentHandler handler = new SheetHandler(sst);
  55. parser.setContentHandler(handler);
  56. return parser;
  57. }
  58. /**
  59. * See org.xml.sax.helpers.DefaultHandler javadocs 重写 startElement characters endElements方法 
  60. */
  61. private static class SheetHandler extends DefaultHandler {
  62. private SharedStringsTable sst;
  63. private String lastContents;
  64. private boolean nextIsString; //是否为string格式标识
  65. private final LruCache<Integer,String> lruCache = new LruCache<>(60);
  66. /*private int sheetIndex = -1;
  67. private int curRow = 0;
  68. private int curCol = 0;
  69. private List<String> rowlist = new ArrayList<String>(); */
  70. /**
  71. * 缓存
  72. * @author Administrator
  73. *
  74. * @param <A>
  75. * @param <B>
  76. */
  77. private static class LruCache<A,B> extends LinkedHashMap<A, B> {
  78. private final int maxEntries;
  79. public LruCache(final int maxEntries) {
  80. super(maxEntries + 1, 1.0f, true);
  81. this.maxEntries = maxEntries;
  82. }
  83. @Override
  84. protected boolean removeEldestEntry(final Map.Entry<A, B> eldest) {
  85. return super.size() > maxEntries;
  86. }
  87. }
  88. private SheetHandler(SharedStringsTable sst) {
  89. this.sst = sst;
  90. }
  91. /**
  92. * 该方法自动被调用,每读一行调用一次,在方法中写自己的业务逻辑即可
  93. * @param sheetIndex 工作簿序号
  94. * @param curRow 处理到第几行
  95. * @param rowList 当前数据行的数据集合
  96. */
  97. /* public void optRow(int sheetIndex, int curRow, List<String> rowList) {
  98. String temp = "";
  99. for(String str : rowList) {
  100. temp += str + "_";
  101. }
  102. this.rowlist.clear();
  103. this.curRow++;
  104. this.curCol=0;
  105. System.out.println(temp);
  106. } */
  107. @Override
  108. public void startElement(String uri, String localName, String name,
  109. Attributes attributes) throws SAXException {
  110. // c => cell 代表单元格
  111. if(name.equals("c")) {
  112. // Print the cell reference
  113. //获取单元格的位置,如A1,B1
  114. System.out.print(attributes.getValue("r") + " - ");
  115. // Figure out if the value is an index in the SST 如果下一个元素是 SST 的索引,则将nextIsString标记为true
  116. //单元格类型
  117. String cellType = attributes.getValue("t");
  118. //cellType值 s:字符串 b:布尔 e:错误处理
  119. if(cellType != null && cellType.equals("s")) {
  120. //标识为true 交给后续endElement处理
  121. nextIsString = true;
  122. } else {
  123. nextIsString = false;
  124. }
  125. }
  126. // Clear contents cache
  127. lastContents = "";
  128. }
  129. /**
  130. * 得到单元格对应的索引值或是内容值
  131. * 如果单元格类型是字符串、INLINESTR、数字、日期,lastIndex则是索引值
  132. * 如果单元格类型是布尔值、错误、公式,lastIndex则是内容值
  133. */
  134. @Override
  135. public void characters(char[] ch, int start, int length)
  136. throws SAXException {
  137. lastContents += new String(ch, start, length);
  138. }
  139. @Override
  140. public void endElement(String uri, String localName, String name)
  141. throws SAXException {
  142. // Process the last contents as required.
  143. // Do now, as characters() may be called more than once
  144. if(nextIsString) {
  145. int idx = Integer.parseInt(lastContents);
  146. lastContents = lruCache.get(idx);
  147. //如果内容为空 或者Cache中存在相同key 不保存到Cache中
  148. if(lastContents == null &&!lruCache.containsKey(idx)){
  149. lastContents = new XSSFRichTextString(sst.getEntryAt(idx)).toString();
  150. lruCache.put(idx, lastContents);
  151. }
  152. nextIsString = false;
  153. }
  154. // v => contents of a cell
  155. // Output after we've seen the string contents
  156. if(name.equals("v")) {
  157. System.out.println(lastContents);
  158. //rowlist.add(curCol++,lastContents);
  159. }else{
  160. //如果标签名称为 row , 已到行尾
  161. if(name.equals("row")){
  162. //optRow(sheetIndex, curRow, rowlist);
  163. System.out.println(lruCache);
  164. lruCache.clear();
  165. }
  166. }
  167. }
  168. }
  169. public static void main(String[] args) throws Exception {
  170. new ExampleEventUserModel().processFirstSheet("F:/temp/template.xlsx");
  171. }
  172. }

SheetHandler类说明:程序依次调用重写的startElement,characters,endElement方法

 

小结

excel2007后采用了基于XML的ooxml开放文档标准,通过操作原始xml数据的方法获得数据。

 

三、User API (HSSF and XSSF)

 

 

New Workbook 

  1. Workbook wb = new HSSFWorkbook();
  2. ...
  3. try (OutputStream fileOut = new FileOutputStream("workbook.xls")) {
  4. wb.write(fileOut);
  5. }
  6. Workbook wb = new XSSFWorkbook();
  7. ...
  8. try (OutputStream fileOut = new FileOutputStream("workbook.xlsx")) {
  9. wb.write(fileOut);
  10. }

 

Files vs InputStreams

在打开一本工作簿时,不管是一个.xls HSSFWorkbook,还是一个.xlsx XSSFWorkbook,工作簿都可以从文件或InputStream中加载。使用File对象可以降低内存消耗,而InputStream则需要更多的内存,因为它必须缓冲整个文件。

如果使用WorkbookFactory,它很容易使用其中一个或另一个:

  1. // Use a file
  2. Workbook wb = WorkbookFactory.create(new File("MyExcel.xls"));
  3. // Use an InputStream, needs more memory
  4. Workbook wb = WorkbookFactory.create(new FileInputStream("MyExcel.xlsx"));

 

如果直接使用HSSFWorkbook或XSSFWorkbook,您通常应该通过 NPOIFSFileSystemOPCPackage的使用来完全控制生命周期(包括在完成时关闭文件):

 

  1. // HSSFWorkbook, File
  2. NPOIFSFileSystem fs = new NPOIFSFileSystem(new File("file.xls"));
  3. HSSFWorkbook wb = new HSSFWorkbook(fs.getRoot(), true);
  4. ....
  5. fs.close();
  6. // HSSFWorkbook, InputStream, needs more memory
  7. NPOIFSFileSystem fs = new NPOIFSFileSystem(myInputStream);
  8. HSSFWorkbook wb = new HSSFWorkbook(fs.getRoot(), true);
  9. // XSSFWorkbook, File
  10. OPCPackage pkg = OPCPackage.open(new File("file.xlsx"));
  11. XSSFWorkbook wb = new XSSFWorkbook(pkg);
  12. ....
  13. pkg.close();
  14. // XSSFWorkbook, InputStream, needs more memory
  15. OPCPackage pkg = OPCPackage.open(myInputStream);
  16. XSSFWorkbook wb = new XSSFWorkbook(pkg);
  17. ....
  18. pkg.close();

 行列操作

  1. Workbook wb = WorkbookFactory.create(inputStream);
  2. Sheet sheet = wb.getSheetAt(0);
  3. //行数
  4. int lastRowNum = sheet.getPhysicalNumberOfRows();
  5. //读取body
  6. for(int rowNum = 1;rowNum <lastRowNum;rowNum++){
  7. //获得当前行
  8. Row row = sheet.getRow(rowNum); //index序列号从0开始,跳过首行,取1
  9. //当前列数
  10. int lastCellNum = row.getPhysicalNumberOfCells();
  11. for(int cellNum=0;cellNum<lastCellNum;cellNum++){
  12. //当前行的某列,确定小方格
  13. Cell cell = row.getCell(cellNum);
  14. String str = cell.getStringCellValue();//获取字符值,当然提供不同的接口获得不同类型值
  15. // TODO
  16. }
  17. wb.close();
  18. }

 

小结:

数据量比较小使用NPOIFSFileSystem或OPCPackage来操作excel,并尽可能使用文件对象参数

 

参考 apache poi官网文档 (随版本更新,api可能有变动)

 

 

 

 

 

 

 

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/88866
推荐阅读
相关标签
  

闽ICP备14008679号