POI对于POI无法处理超大xls等文件,官方解决方法未研究,待定

Posted Angel挤一挤

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了POI对于POI无法处理超大xls等文件,官方解决方法未研究,待定相关的知识,希望对你有一定的参考价值。

本次使用POI处理xlsx文件,莫名的遇到了一个无法逾越的问题。

总共71个xlsx文件,单个文件最大达到50M以上,71个xls文件摆在那里就有3-4G的大小。

 在起始处理的时候,发现原本适用于正常大小的POI处理xls程序竟然屡次的报错GC outofmemory 的内存移除的问题。

【当前状况】

①一个50M大小的xlsx文件,使用压缩文件打开,可以看到xml文件达到900M以上

②一个50M大小以上的xlsx文件,单个工作簿,行数平均在15W行---40W之间,列数在64列左右

③单方面的调整了JVM运行的  堆内存大小,调整到了4G并没有什么效果

④程序运行起来之后,XSSFWorkbook workbook1 = new XSSFWorkbook(fileInputStream); 跑起来,CPU瞬间99%满负荷,内存最高可以使用9.79G的占用率,仅这一个xlsx文件

【官方解决方法】

网上关于POI处理超大文件的资料不很多,粘贴,水贴更是多如牛毛。POI官网,有一个SXSSF的XSSF扩展API插件,话说是可以【生成/处理】【有歧义】大型电子表格。

 

 

 

但是,仅仅是支持生成而已。

如果用于操作大型xlsx文件,API中给出的构造方法,还是需要先构造XSSFWorkbook对象。

 

在上一步就会卡死导致内存溢出,根本走不到下步。

 

 

最后,还是找到了一篇有用的信息,关于官方的解决思路,就是将xlsx文件转化为CVS文件进行相应的处理,这样不用占用太大的内存空间,导致内存溢出程序崩溃。

一下是官方提供的XLS转化为CVS的代码,注解做了部分翻译。留下之后有时间再进行研究。

  1 package com.poi.dealXlsx;
  2 
  3 import org.apache.poi.openxml4j.exceptions.OpenXML4JException;  
  4 
  5 /* ==================================================================== 
  6         Licensed to the Apache Software Foundation (ASF) under one or more 
  7         contributor license agreements.  See the NOTICE file distributed with 
  8         this work for additional information regarding copyright ownership. 
  9         The ASF licenses this file to You under the Apache License, Version 2.0 
 10         (the "License"); you may not use this file except in compliance with 
 11         the License.  You may obtain a copy of the License at 
 12  
 13         http://www.apache.org/licenses/LICENSE-2.0 
 14  
 15         Unless required by applicable law or agreed to in writing, software 
 16         distributed under the License is distributed on an "AS IS" BASIS, 
 17         WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 18         See the License for the specific language governing permissions and 
 19         limitations under the License. 
 20         ==================================================================== */  
 21   
 22   
 23 import java.io.File;  
 24 import java.io.IOException;  
 25 import java.io.InputStream;  
 26 import java.io.PrintStream;  
 27   
 28 import javax.xml.parsers.ParserConfigurationException;  
 29   
 30 import org.apache.poi.openxml4j.opc.OPCPackage;  
 31 import org.apache.poi.openxml4j.opc.PackageAccess;  
 32 import org.apache.poi.ss.usermodel.DataFormatter;  
 33 import org.apache.poi.ss.util.CellAddress;  
 34 import org.apache.poi.ss.util.CellReference;  
 35 import org.apache.poi.util.SAXHelper;  
 36 import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable;  
 37 import org.apache.poi.xssf.eventusermodel.XSSFReader;  
 38 import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;  
 39 import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler.SheetContentsHandler;  
 40 import org.apache.poi.xssf.extractor.XSSFEventBasedExcelExtractor;  
 41 import org.apache.poi.xssf.model.StylesTable;  
 42 import org.apache.poi.xssf.usermodel.XSSFComment;  
 43 import org.xml.sax.ContentHandler;  
 44 import org.xml.sax.InputSource;  
 45 import org.xml.sax.SAXException;  
 46 import org.xml.sax.XMLReader;  
 47   
 48 /** 
 49      *一个基本的XLSX - > CSV处理器
 50      * POI样本程序XLS2CSVmra从包中
 51      * org.apache.poi.hssf.eventusermodel.examples。
 52      *与HSSF版本一样,这会试图找到错误
 53      *行和单元格,并为它们输出空条目。
 54      * <p />
 55      *使用SAX解析器读取数据表以保持
 56      *内存占用比较小,所以这应该是
 57      *能够阅读庞大的工作簿。样式表和
 58      *共享字符串表必须保存在内存中。该
 59      *标准POI样式表类使用,但是一个自定义
 60      *(只读)类用于共享字符串表
 61      *因为标准的POI SharedStringsTable增长很大
 62      *快速与唯一字符串的数量。
 63      * <p />
 64      *更高级的SAX事件解析实现
 65      *的XLSX文件,请参阅{@link XSSFEventBasedExcelExtractor}
 66      *和{@link XSSFSheetXMLHandler}。请注意,在很多情况下,
 67      *可以简单地使用那些与习惯
 68      * {@link SheetContentsHandler}并且不需要SAX代码
 69      * 你自己! 
 70  */  
 71 public class XLSX2CSV {  
 72     /**
 73           *使用XSSF Event SAX助手进行大部分工作
 74           *解析Sheet XML,并输出内容
 75           *作为(基本)CSV。
 76          */ 
 77     private class SheetToCSV implements SheetContentsHandler {  
 78         private boolean firstCellOfRow = false;  
 79         private int currentRow = -1;  
 80         private int currentCol = -1;  
 81   
 82         /**
 83          * 输出缺失的行
 84          * @param number
 85          */
 86         private void outputMissingRows(int number) {  
 87             for (int i = 0; i < number; i++) {  
 88                 for (int j = 0; j < minColumns; j++) {  
 89                     output.append(\',\');  
 90                 }  
 91                 output.append(\'\\n\');  
 92             }  
 93         }  
 94   
 95         @Override  
 96         public void startRow(int rowNum) {  
 97             // If there were gaps, output the missing rows  
 98             outputMissingRows(rowNum - currentRow - 1);  
 99             // Prepare for this row  
100             firstCellOfRow = true;  
101             currentRow = rowNum;  
102             currentCol = -1;  
103         }  
104   
105         @Override  
106         public void endRow(int rowNum) {  
107             // Ensure the minimum number of columns  
108             for (int i = currentCol; i < minColumns; i++) {  
109                 output.append(\',\');  
110             }  
111             output.append(\'\\n\');  
112         }  
113   
114         @Override  
115         public void cell(String cellReference, String formattedValue,XSSFComment comment) {  
116             if (firstCellOfRow) {  
117                 firstCellOfRow = false;  
118             } else {  
119                 output.append(\',\');  
120             }  
121   
122             // gracefully handle missing CellRef here in a similar way as XSSFCell does  
123             if (cellReference == null) {  
124                 cellReference = new CellAddress(currentRow, currentCol).formatAsString();  
125             }  
126   
127             // Did we miss any cells?  
128             int thisCol = (new CellReference(cellReference)).getCol();  
129             int missedCols = thisCol - currentCol - 1;  
130             for (int i = 0; i < missedCols; i++) {  
131                 output.append(\',\');  
132             }  
133             currentCol = thisCol;  
134   
135             // Number or string?  
136             try {  
137                 Double.parseDouble(formattedValue);  
138                 output.append(formattedValue);  
139             } catch (NumberFormatException e) {  
140                 output.append(\'"\');  
141                 output.append(formattedValue);  
142                 output.append(\'"\');  
143             }  
144         }  
145   
146         @Override  
147         public void headerFooter(String text, boolean isHeader, String tagName) {  
148             // Skip, no headers or footers in CSV  
149         }  
150     }  
151   
152 
153   
154     /**
155      * 表示可以存储多个数据对象的容器。
156      */
157     private final OPCPackage xlsxPackage;  
158   
159     /** 
160      * 以最左边开始读取的列数
161      */  
162     private final int minColumns;  
163   
164     /** 
165      * 写出数据流
166      */  
167     private final PrintStream output;  
168   
169     /** 
170      * 创建一个新的XLSX -> CSV转换器
171      * 
172      * @param pkg        The XLSX package to process 
173      * @param output     The PrintStream to output the CSV to 
174      * @param minColumns 要输出的最小列数,或-1表示最小值
175      */  
176     public XLSX2CSV(OPCPackage pkg, PrintStream output, int minColumns) {  
177         this.xlsxPackage = pkg;  
178         this.output = output;  
179         this.minColumns = minColumns;  
180     }  
181   
182     /** 
183      * Parses and shows the content of one sheet 
184      * using the specified styles and shared-strings tables. 
185      *解析并显示一个工作簿的内容
186             *使用指定的样式和共享字符串表。
187      * @param styles     工作簿中所有工作表共享的样式表。
188      * @param strings     这是处理共享字符串表的轻量级方式。 大多数文本单元格将引用这里的内容。请注意,如果字符串由不同格式的位组成,则每个SI条目都可以有多个T元素
189      * @param sheetInputStream         
190      */  
191     public void processSheet(StylesTable styles,
192             ReadOnlySharedStringsTable strings,
193             SheetContentsHandler sheetHandler, InputStream sheetInputStream)
194             throws IOException, ParserConfigurationException, SAXException {
195         DataFormatter formatter = new DataFormatter();
196         InputSource sheetSource = new InputSource(sheetInputStream);
197         try {
198             XMLReader sheetParser = SAXHelper.newXMLReader();
199             ContentHandler handler = new XSSFSheetXMLHandler(styles, null,
200                     strings, sheetHandler, formatter, false);
201             sheetParser.setContentHandler(handler);
202             sheetParser.parse(sheetSource);
203         } catch (ParserConfigurationException e) {
204             throw new RuntimeException("SAX parser appears to be broken - "
205                     + e.getMessage());
206         }
207     }
208   
209     /** 
210      * Initiates the processing of the XLS workbook file to CSV. 
211      * 启动将XLS工作簿文件处理为CSV。
212      * 
213      * @throws IOException 
214      * @throws OpenXML4JException 
215      * @throws ParserConfigurationException 
216      * @throws SAXException 
217      */  
218     public void process()  
219             throws IOException, OpenXML4JException, ParserConfigurationException, SAXException {  
220         ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(this.xlsxPackage);  
221         XSSFReader xssfReader = new XSSFReader(this.xlsxPackage);  
222         StylesTable styles = xssfReader.getStylesTable();  
223         XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader.getSheetsData();  
224         int index = 0;  
225         while (iter.hasNext()) {  
226             InputStream stream = iter.next();  
227             String sheetName = iter.getSheetName();  
228             this.output.println();  
229             this.output.println(sheetName + " [index=" + index + "]:");  
230             processSheet(styles, strings, new SheetToCSV(), stream);  
231             stream.close();  
232             ++index;  
233         }  
234     }  
235   
236     public static void main(String[] args) throws Exception {  
237       /*  if (args.length < 1) { 
238             System.err.println("Use:"); 
239             System.err.println("  XLSX2CSV <xlsx file> [min columns]"); 
240             return; 
241         }*/  
242   
243         File xlsxFile = new File("D:/基因数据测试/S1.xlsx");  
244         if (!xlsxFile.exists()) {  
245             System.err.println("没找到文件: " + xlsxFile.getPath());  
246             return;  
247         }  
248   
249         int minColumns = -1;  
250         if (args.length >= 2)  
251             minColumns = Integer.parseInt(args[1]);  
252   
253         // The package open is instantaneous, as it should be.  
254         OPCPackage p = OPCPackage.open(xlsxFile.getPath(), PackageAccess.READ);  
255         XLSX2CSV xlsx2csv = new XLSX2CSV(p, System.out, minColumns);  
256         xlsx2csv.process();  
257         p.close();  
258     }  
259 } 
View Code

 

----------------------------------------------------------------------------------【待续1】------------------------------------------------------------------------------------------

 

上回书说道,官方提供的XLS转化为CVS代码,其实就是使用org.xml.sax.XMLReader使用SAX解析器去解析了xls底层的xml文件而已。并没有真正的转化为CVS文件。

【XMLReader就是JDK自带的方法,查阅API可以查看相关实现类和相关工具类的方法和使用】

对于上段代码的研读,贴出来:

  1 package com.poi.dealXlsx;
  2 
  3 import org.apache.poi.openxml4j.exceptions.OpenXML4JException;  
  4 import java.io.File;  
  5 import java.io.IOException;  
  6 import java.io.InputStream;  
  7 import java.io.PrintStream;  
  8   
  9 
 10 import javax.xml.parsers.ParserConfigurationException;  
 11   
 12 
 13 import org.apache.poi.openxml4j.opc.OPCPackage;  
 14 import org.apache.poi.openxml4j.opc.PackageAccess;  
 15 import org.apache.poi.ss.usermodel.DataFormatter;  
 16 import org.apache.poi.ss.util.CellAddress;  
 17 import org.apache.poi.ss.util.CellReference;  
 18 import org.apache.poi.util.SAXHelper;  
 19 import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable;  
 20 import org.apache.poi.xssf.eventusermodel.XSSFReader;  
 21 import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;  
 22 import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler.SheetContentsHandler;  
 23 import org.apache.poi.xssf.model.StylesTable;  
 24 import org.apache.poi.xssf.usermodel.XSSFComment;  
 25 import org.xml.sax.ContentHandler;  
 26 import org.xml.sax.InputSource;  
 27 import org.xml.sax.SAXException;  
 28 import org.xml.sax.XMLReader;  
 29   
 30 /**
 31  *  一个基本的XLSX - > CSV处理器 POI样本程序XLS2CSVmra从包中
 32  *  实际上是将xlsx文件从传统的POI解析 转变为将xlsx底层的xml文件交给SAX解析器去解析
 33  *  以便处理超大xlsx文件,从而占用更小的内存和CPU资源
 34  */  
 35 public class XLSX2CSV {  
 36     
 37     /**
 38      * XSSFSheetXMLHandler处理器,用来处理xlsx文件底层xml文件的sheet部分
 39      */ 
 40     private class SheetToCSV implements SheetContentsHandler {  
 41         private boolean firstCellOfRow = false;  
 42         private int currentRow = -1;  
 43         private int currentCol = -1;  
 44   
 45         /**
 46          * 输出缺失的行
 47          * @param number
 48          */
 49         private void outputMissingRows(int number) {  
 50             for (int i = 0; i < number; i++) {  
 51                 for (int j = 0; j < minColumns; j++) {  
 52                     output.append(\',\');  
 53                 }  
 54                 output.println("缺失一行");  
 55             }  
 56         }  
 57   
 58         //重新开始一行
 59         @Override  
 60         public void startRow(int rowNum) {  
 61             outputMissingRows(rowNum - currentRow - 1);  
 62             firstCellOfRow = true;  
 63             currentRow = rowNum;  
 64             currentCol = -1;  
 65         }  
 66   
 67         @Override  
 68         public void endRow(int rowNum) {  
 69             //确保最小的列数
 70             for (int i = currentCol; i < minColumns; i++) {  
 71                 output.println(rowNum+"行结束");  
 72             }  
 73             output.println("下标:"+rowNum+"行结束"); 
 74         }  
 75   
 76         @Override  
 77         public void cell(String cellReference, String formattedValue,XSSFComment comment) {  
 78             // 以下处理了单元格为Null的情况
 79             //通过【A1】等判断是是【第一行第1列】
 80             int thisCol = (new CellReference(cellReference)).getCol();  
 81             int missedCols = thisCol - currentCol - 1;  
 82             for (int i = 0; i < missedCols; i++) {  
 83                 output.println("单元格值为空"); 
 84             } 
 85             currentCol = thisCol;
 86             if (firstCellOfRow) {  
 87                 firstCellOfRow = false;  
 88                 output.println(currentRow+"行的第一个单元格");
 89             } 
 90   
 91             
 92             //cellReference代表单元格的横纵坐标
 93             if (cellReference == null) {  
 94                 cellReference = new CellAddress(currentRow, currentCol).formatAsString();  
 95             }  
 96   
 97             output.println("行"+currentRow+"+列"+currentCol+">>值:"+formattedValue);
 98         }  
 99   
100         @Override  
101         public void headerFooter(String text, boolean isHeader, String tagName) {  
102             // CSV文件中没有页眉页脚 
103         }  
104     }  
105   
106   
107     /**
108      * 表示可以存储多个数据对象的容器。
109      */
110     private final OPCPackage xlsxPackage;  
111   
112     /** 
113      * 以最左边开始读取的列数
114      */  
115     private final int minColumns;  
116   
117     /** 
118      * 写出数据流
119      */  
120     private final PrintStream output;  
121   
122     /** 
123      * 创建一个新的XLSX -> CSV转换器
124      * 
125      * @paramJava 处理 Excel:从 POI 到 SPL

Java处理Excel:从POI到SPL

使用java读取xlsx格式的execl,除了poi还都有哪些方式

Java处理Excel:从POI到SPL

Android使用poi读取创建另存Excel,支持xlsx和部分xls格式

xssfworkbook 怎么解决科学计算法