【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      * @param pkg        The XLSX package to process 
126      * @param output     The PrintStream to output the CSV to 
127      * @param minColumns 要輸出的最小列數,或-1表示最小值
128      */  
129     public XLSX2CSV(OPCPackage pkg, PrintStream output, int minColumns) {  
130         this.xlsxPackage = pkg;  
131         this.output = output;  
132         this.minColumns = minColumns;  
133     }  
134   
135     /** 
136      *解析並顯示一個工作簿的內容
137      * @param styles                 工作簿中所有工作表共享的樣式表。
138      * @param readOnlyTableString     這是處理共享字符串表的輕量級方式。 大多數文本單元格將引用這里的內容。請注意,如果字符串由不同格式的位組成,則每個SI條目都可以有多個T元素
139      * @param sheetInputStream         以sheet為單位的輸入流  
140      */  
141     public void processSheet(StylesTable styles,ReadOnlySharedStringsTable readOnlyTableString,SheetContentsHandler sheetHandler, InputStream sheetInputStream)
142             throws IOException, ParserConfigurationException, SAXException {
143         
144         DataFormatter formatter = new DataFormatter();
145         InputSource sheetSource = new InputSource(sheetInputStream);
146         try {
147             //為了盡可能的節省內存和I/0消耗,XmlReader讀取Xml需要通過Read()實例方法,不斷讀取Xml文檔中的聲明,節點開始,節點內容,節點結束,以及空白等等,直到文檔結束,Read()方法返回false
148             XMLReader reader = SAXHelper.newXMLReader();
149             //XSSFSheetXMLHandler處理器,用來處理xlsx文件底層xml文件的sheet部分,用於生成行和單元格事件
150             ContentHandler handler = new XSSFSheetXMLHandler(styles, null,readOnlyTableString, sheetHandler, formatter, false);
151             //允許應用程序注冊內容事件處理程序
152             reader.setContentHandler(handler);
153             //解析XML文件
154             reader.parse(sheetSource);
155         } catch (ParserConfigurationException e) {
156             throw new RuntimeException("SAX解析器壞了"+ e.getMessage());
157         }
158     }
159   
160     /** 
161      * Initiates the processing of the XLS workbook file to CSV. 
162      * 啟動將XLS工作簿文件處理為CSV。
163      * 
164      * @throws IOException 
165      * @throws OpenXML4JException 
166      * @throws ParserConfigurationException 
167      * @throws SAXException 
168      */  
169     public void process()throws IOException, OpenXML4JException, ParserConfigurationException, SAXException { 
170         
171         //用於構建XSSFSheetXMLHandler處理器所需要實例化的參數
172         ReadOnlySharedStringsTable readOnlyTableString = new ReadOnlySharedStringsTable(this.xlsxPackage);  
173         //XSSFReader獲取xlsx文件xml下的各個部分,適用於低內存sax解析或類似。 它構成了對XSSF的EventUserModel支持的核心部分。
174         XSSFReader xssfReader = new XSSFReader(this.xlsxPackage); 
175         //工作簿中所有工作表共享的樣式表
176         StylesTable styles = xssfReader.getStylesTable(); 
177         //從org.apache.poi.xssf.eventusermodel.XSSFReader中獲取到Sheet中的數據用來迭代,交給SAX去解析
178         XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader.getSheetsData();  
179         int index = 0;  
180         //此處迭代以 sheet為單位,一個工作簿進行一次解析
181         while (iter.hasNext()) {  
182             InputStream inputStream = iter.next();  
183             String sheetName = iter.getSheetName();  
184             this.output.println("開始解析》》》》》》》》》》》》》》》》》》》》");  
185             this.output.println("工作簿名稱:"+sheetName + " [下標=" + index + "]");  
186             processSheet(styles, readOnlyTableString, new SheetToCSV(), inputStream);  
187             inputStream.close();  
188             ++index;  
189         }  
190     }  
191   
192     public static void main(String[] args) throws Exception {  
193         File xlsxFile = new File("D:/基因數據測試/S1.xlsx");  
194         if (!xlsxFile.exists()) {  
195             System.err.println("沒找到文件: " + xlsxFile.getPath());  
196             return;  
197         }  
198   
199         int minColumns = -1;  
200   
201         //打開一個POI容器   參數1 文檔路徑  參數2 READ  READ_WRITE  WRITE三種模式
202         OPCPackage p = OPCPackage.open(xlsxFile.getPath(), PackageAccess.READ);  
203         
204         //自己創建的xls->cvs的轉化器
205         XLSX2CSV xlsx2csv = new XLSX2CSV(p, System.out, minColumns);  
206         xlsx2csv.process();  
207         //關閉容器
208         p.close();  
209     }  
210 } 
View Code

 

實際測試的,就是下面的文件:

文件大小51M大小左右

 

 

 

對於上面這段官方提供的方法,我的處理思想是:

將xlsx中的數據讀取出來之后,進行每1000行數據一次拆分的做法,將上述較大的xls文件拆分為一個一個的小文件,然后使用 http://www.cnblogs.com/sxdcgaq8080/p/7344787.html

提供的方法迭代去處理文件數據。

明天,將具體的處理代碼寫出來。

----------------------------------------------------------------------------------【待續2】------------------------------------------------------------------------------------------

 處理了將近兩天的時間,終於把xlsx超大文件的拆解弄出來了。

  1 package com.poi.dealXlsx;
  2 
  3 import org.apache.poi.openxml4j.exceptions.OpenXML4JException;  
  4 
  5 import java.io.File;  
  6 import java.io.FileInputStream;
  7 import java.io.FileNotFoundException;
  8 import java.io.FileOutputStream;
  9 import java.io.IOException;  
 10 import java.io.InputStream;  
 11 import java.io.PrintStream;  
 12   
 13 
 14 
 15 
 16 
 17 
 18 import java.util.ArrayList;
 19 import java.util.Arrays;
 20 import java.util.Comparator;
 21 import java.util.List;
 22 
 23 import javax.xml.parsers.ParserConfigurationException;  
 24   
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 import org.apache.poi.openxml4j.opc.OPCPackage;  
 37 import org.apache.poi.openxml4j.opc.PackageAccess;  
 38 import org.apache.poi.ss.usermodel.Cell;
 39 import org.apache.poi.ss.usermodel.DataFormatter;  
 40 import org.apache.poi.ss.usermodel.Row;
 41 import org.apache.poi.ss.usermodel.Sheet;
 42 import org.apache.poi.ss.util.CellAddress;  
 43 import org.apache.poi.ss.util.CellReference;  
 44 import org.apache.poi.util.SAXHelper;  
 45 import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable;  
 46 import org.apache.poi.xssf.eventusermodel.XSSFReader;  
 47 import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;  
 48 import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler.SheetContentsHandler;  
 49 import org.apache.poi.xssf.model.StylesTable;  
 50 import org.apache.poi.xssf.usermodel.XSSFComment;  
 51 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 52 import org.xml.sax.ContentHandler;  
 53 import org.xml.sax.InputSource;  
 54 import org.xml.sax.SAXException;  
 55 import org.xml.sax.XMLReader;  
 56   
 57 /**
 58  *  一個基本的XLSX - > CSV處理器 POI樣本程序XLS2CSVmra從包中
 59  *  實際上是將xlsx文件從傳統的POI解析 轉變為將xlsx底層的xml文件交給SAX解析器去解析
 60  *  以便處理超大xlsx文件,從而占用更小的內存和CPU資源
 61  */  
 62 public class XLSX2CSV {  
 63     
 64     /**
 65      * XSSFSheetXMLHandler處理器,用來處理xlsx文件底層xml文件的sheet部分
 66      */ 
 67     private class SheetToCSV implements SheetContentsHandler {  
 68         private boolean firstCellOfRow = false;  
 69         private int currentRow = -1;  
 70         private int currentCol = -1;  
 71         private List<String> firstRowValue = new ArrayList<String>();
 72         /**
 73          * 輸出缺失的行
 74          * @param number
 75          */
 76         private void outputMissingRows(int number) {  
 77             for (int i = 0; i < number; i++) {  
 78                 for (int j = 0; j < minColumns; j++) {  
 79                     output.append(',');  
 80                 }  
 81                 output.println("缺失一行");  
 82             }  
 83         }  
 84   
 85         //重新開始一行
 86         @Override  
 87         public void startRow(int rowNum) {  
 88             outputMissingRows(rowNum - currentRow - 1);  
 89             firstCellOfRow = true;  
 90             currentRow = rowNum;  
 91             currentCol = -1;  
 92         }  
 93   
 94         @Override  
 95         public void endRow(int rowNum) {  
 96             //確保最小的列數
 97             for (int i = currentCol; i < minColumns; i++) {  
 98                 output.println(rowNum+"行結束");  
 99             }  
100             output.println("下標:"+rowNum+"行結束"); 
101         }  
102   
103         
104         /**
105          * 邏輯處理的地方
106          */
107         @Override  
108         public void cell(String cellReference, String formattedValue,XSSFComment comment) {
109             
110             // 以下處理了單元格為Null的情況
111             //通過【A1】等判斷是是【第一行第1列】
112             int thisCol = (new CellReference(cellReference)).getCol();  
113             int missedCols = thisCol - currentCol - 1;  
114             for (int i = 0; i < missedCols; i++) {  
115                 output.println("單元格值為空"); 
116             } 
117             currentCol = thisCol;
118             if (firstCellOfRow) {  
119                 firstCellOfRow = false;  
120                 output.println(currentRow+"行的第一個單元格");
121             } 
122   
123             
124             //cellReference代表單元格的橫縱坐標
125             if (cellReference == null) {  
126                 cellReference = new CellAddress(currentRow, currentCol).formatAsString();  
127             }  
128             //打印出行列中的值
129 //            output.println("行"+currentRow+"+列"+currentCol+">>值:"+formattedValue);
130             //單獨把第一行 也就是列名 全部存儲起來,用於插入每一個拆解開的xls文件中
131             if(currentRow == 0){
132                 //如果是第一行的第一列  說明重新讀取了一個新的原始文件  需要重新存儲列名
133                 if(currentCol == 0){
134                     firstRowValue.clear();
135                 }
136                 firstRowValue.add(formattedValue);
137                 
138             }else{
139                  File file = new File("d:/分解xls");
140                  
141                  //如果currentRow=0的時候應該重新創建文件夾,代表有開始拆解新的xls文件
142                  //否則代表正在拆解當前的xls文件,應該在當前的文件夾中進行
143                  if(currentRow == 0){
144                      file = createDir(true,file);
145                  }else{
146                      file = createDir(false,file);
147                  }
148                  //此處按照每1000行拆解為一個xlsx文件,修改1000,即按照自定義的行數拆解為一個xlsx文件
149                //如果行號模1000=0   需要重新創建一個文件 否則就在當前文件中寫入數據
150                  file = currentRow%1000 == 0 && currentCol == 0 ? getFile(file,true) : getFile(file,false);
151                  
152                  try {
153                      FileInputStream fileInputStream = new FileInputStream(file);
154                      XSSFWorkbook workbook = new XSSFWorkbook(fileInputStream);
155                      Sheet sheet = workbook.getSheetAt(0);
156                      int lastRowNum = sheet.getLastRowNum();//當前xlsx中最大行
157                      Row row = null; 
158                      if(lastRowNum == 1 & currentCol ==1){
159                          row = sheet.createRow(0);
160                          for (int i = 0; i < firstRowValue.size(); i++) {
161                             Cell cell = row.createCell(i);
162                             cell.setCellValue(firstRowValue.get(i));
163                         }
164                      }
165                      if(lastRowNum == 0){
166                          row = currentCol != 0 ? sheet.getRow(1):sheet.createRow(1);
167                      }else{
168                          row = currentCol != 0 ? sheet.getRow(lastRowNum):sheet.createRow(lastRowNum+1);
169                      }
170                      //為本單元格 賦值
171                      Cell cell = row.createCell(currentCol);
172                      cell.setCellValue(formattedValue);
173                      FileOutputStream fileOutputStream = new FileOutputStream(file);
174                      workbook.write(fileOutputStream);
175                      fileOutputStream.close();
176                      workbook.close();
177                      
178                  } catch (FileNotFoundException e) {
179                      output.println("不能獲取到xlsx文件");
180                  } catch (IOException e) {
181                      output.println("不是xlsx文件");
182                  }
183             }
184             
185         }  
186   
187         
188         @Override  
189         public void headerFooter(String text, boolean isHeader, String tagName) {  
190             // CSV文件中沒有頁眉頁腳 
191         }  
192         
193         /**
194          * 得到要操作的xlsx文件
195          * @param file        文件目錄
196          * @param flag        是否需要重新創建一個新的xlsx
197          * @return
198          */
199         private File getFile(File file,boolean flag){
200             File [] fileList = file.listFiles();
201             Arrays.sort(fileList,new OrderFile());
202             String newDirName = "1";
203             if(fileList.length != 0){
204                 if(flag){
205                     try {
206                         newDirName = String.valueOf(Integer.parseInt(fileList[fileList.length-1].getName().substring(0, fileList[fileList.length-1].getName().indexOf(".")))+1);
207                     } catch (Exception e) {
208                         System.out.println("文件名獲取失敗");
209                     }
210                 }else{
211                     return fileList[fileList.length-1];
212                 }
213                 
214             }
215             String filePath = file.getAbsolutePath()+"/"+newDirName+".xlsx";
216             XSSFWorkbook  workbook = new XSSFWorkbook();
217             Sheet sheet = workbook.createSheet(newDirName);
218             try {
219                 FileOutputStream out = new FileOutputStream(filePath);
220                 workbook.write(out);
221                 System.out.println("創建xls文件"+newDirName+".xlsx成功");
222             } catch (FileNotFoundException e) {
223                 System.out.println("創建xls失敗");
224             } catch (IOException e) {
225                 System.out.println("創建xls失敗");
226             }
227             return new File(filePath);
228         }
229         
230         
231         /**
232          * 得到當前要操作的目錄
233          * @param sign        標志 是否需要新創建一個文件夾
234          * @param file        文件夾路徑
235          * @return
236          */
237         private File createDir(boolean sign,File file){
238             File [] fileList = file.listFiles();
239             Arrays.sort(fileList,new OrderFileS());
240             if(fileList.length == 0){
241                 File thisFile = new File(file.getAbsolutePath()+"/1/");
242                 boolean flag = thisFile.mkdirs();
243                 System.out.println("創建文件夾1"+flag);
244                 return thisFile;
245             }else{
246                 if(sign){
247                     try {
248                          String newDirName = String.valueOf(Integer.parseInt(fileList[fileList.length-1].getName())+1);
249                          File thisFile = new File(file.getAbsolutePath()+"/"+newDirName+"/");
250                          boolean flag = thisFile.mkdirs();
251                          System.out.println("創建文件夾"+newDirName+flag);
252                          return thisFile;
253                     } catch (Exception e) {
254                         System.out.println("順序最后一個文件夾名字獲取失敗,類型轉化失敗");
255                     }        
256                 }
257             }
258             return fileList[fileList.length-1];
259         }
260     }  
261     
262     /**
263      * 重寫Comparator接口的compare方法,可以根據FileName實現自定義排序
264      * @author SXD
265      *
266      */
267     class OrderFile implements Comparator<File>{
268 
269         @Override
270         public int compare(File o1, File o2) {
271             int fileName1 = 0;
272             int fileName2 = 0;
273             try {
274                 fileName1 = Integer.parseInt(o1.getName().substring(0, o1.getName().indexOf(".")));
275                 fileName2 = Integer.parseInt(o2.getName().substring(0, o2.getName().indexOf(".")));
276             } catch (Exception e) {
277                 System.out.println("比較方法中  獲取文件名失敗");
278             }
279             return fileName1>fileName2 ? 1 : fileName1 == fileName2 ? 0 : -1;
280         }
281         
282     }
283     /**
284      * 排序文件夾 
285      * @author SXD
286      *
287      */
288     class OrderFileS implements Comparator<File>{
289         
290         @Override
291         public int compare(File o1, File o2) {
292             int fileName1 = 0;
293             int fileName2 = 0;
294             try {
295                 fileName1 = Integer.parseInt(o1.getName());
296                 fileName2 = Integer.parseInt(o2.getName());
297             } catch (Exception e) {
298                 System.out.println("比較方法中  獲取文件夾名失敗");
299             }
300             return fileName1>fileName2 ? 1 : fileName1 == fileName2 ? 0 : -1;
301         }
302         
303     }
304   
305     /**
306      * 表示可以存儲多個數據對象的容器。
307      */
308     private final OPCPackage xlsxPackage;  
309   
310     /** 
311      * 以最左邊開始讀取的列數
312      */  
313     private final int minColumns;  
314   
315     /** 
316      * 寫出數據流
317      */  
318     private final PrintStream output;  
319   
320     /** 
321      * 創建一個新的XLSX -> CSV轉換器
322      * 
323      * @param pkg        The XLSX package to process 
324      * @param output     The PrintStream to output the CSV to 
325      * @param minColumns 要輸出的最小列數,或-1表示最小值
326      */  
327     public XLSX2CSV(OPCPackage pkg, PrintStream output, int minColumns) {  
328         this.xlsxPackage = pkg;  
329         this.output = output;  
330         this.minColumns = minColumns;  
331     }  
332   
333     /** 
334      *解析並顯示一個工作簿的內容
335      * @param styles                 工作簿中所有工作表共享的樣式表。
336      * @param readOnlyTableString     這是處理共享字符串表的輕量級方式。 大多數文本單元格將引用這里的內容。請注意,如果字符串由不同格式的位組成,則每個SI條目都可以有多個T元素
337      * @param sheetInputStream         以sheet為單位的輸入流  
338      */  
339     public void processSheet(StylesTable styles,ReadOnlySharedStringsTable readOnlyTableString,SheetContentsHandler sheetHandler, InputStream sheetInputStream)
340             throws IOException, ParserConfigurationException, SAXException {
341         
342         DataFormatter formatter = new DataFormatter();
343         InputSource sheetSource = new InputSource(sheetInputStream);
344         try {
345             //為了盡可能的節省內存和I/0消耗,XmlReader讀取Xml需要通過Read()實例方法,不斷讀取Xml文檔中的聲明,節點開始,節點內容,節點結束,以及空白等等,直到文檔結束,Read()方法返回false
346             XMLReader reader = SAXHelper.newXMLReader();
347             //XSSFSheetXMLHandler處理器,用來處理xlsx文件底層xml文件的sheet部分,用於生成行和單元格事件
348             ContentHandler handler = new XSSFSheetXMLHandler(styles, null,readOnlyTableString, sheetHandler, formatter, false);
349             //允許應用程序注冊內容事件處理程序
350             reader.setContentHandler(handler);
351             //解析XML文件
352             reader.parse(sheetSource);
353         } catch (ParserConfigurationException e) {
354             throw new RuntimeException("SAX解析器壞了"+ e.getMessage());
355         }
356     }
357   
358     /** 
359      * Initiates the processing of the XLS workbook file to CSV. 
360      * 啟動將XLS工作簿文件處理為CSV。
361      * 
362      * @throws IOException 
363      * @throws OpenXML4JException 
364      * @throws ParserConfigurationException 
365      * @throws SAXException 
366      */  
367     public void process()throws IOException, OpenXML4JException, ParserConfigurationException, SAXException { 
368         
369         //用於構建XSSFSheetXMLHandler處理器所需要實例化的參數
370         ReadOnlySharedStringsTable readOnlyTableString = new ReadOnlySharedStringsTable(this.xlsxPackage);  
371         //XSSFReader獲取xlsx文件xml下的各個部分,適用於低內存sax解析或類似。 它構成了對XSSF的EventUserModel支持的核心部分。
372         XSSFReader xssfReader = new XSSFReader(this.xlsxPackage); 
373         //工作簿中所有工作表共享的樣式表
374         StylesTable styles = xssfReader.getStylesTable(); 
375         //從org.apache.poi.xssf.eventusermodel.XSSFReader中獲取到Sheet中的數據用來迭代,交給SAX去解析
376         XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader.getSheetsData();  
377         int index = 0;  
378         //此處迭代以 sheet為單位,一個工作簿進行一次解析
379         while (iter.hasNext()) {  
380             InputStream inputStream = iter.next();  
381             String sheetName = iter.getSheetName();  
382             this.output.println("開始解析》》》》》》》》》》》》》》》》》》》》");  
383             this.output.println("工作簿名稱:"+sheetName + " [下標=" + index + "]");  
384             processSheet(styles, readOnlyTableString, new SheetToCSV(), inputStream);  
385             inputStream.close();  
386             ++index;  
387         }  
388     }  
389   
390     //可以在這里循環處理多個超大xlsx文件,邏輯中已經處理 一個超大的xlsx文件拆解開的小的xlsx文件會放在一個文件夾中
391     public static void main(String[] args) throws Exception {  
392         /**********這段代碼,處理多個xlsx文件,只要循環去取即可************/
393         File xlsxFile = new File("D:/基因數據測試/S1.xlsx");  
394         if (!xlsxFile.exists()) {  
395             System.err.println("沒找到文件: " + xlsxFile.getPath());  
396             return;  
397         }  
398         /***********************/
399         int minColumns = -1;  
400   
401         //打開一個POI容器   參數1 文檔路徑  參數2 READ  READ_WRITE  WRITE三種模式
402         OPCPackage p = OPCPackage.open(xlsxFile.getPath(), PackageAccess.READ);  
403         
404         //自己創建的xls->cvs的轉化器
405         XLSX2CSV xlsx2csv = new XLSX2CSV(p, System.out, minColumns);  
406         xlsx2csv.process();  
407         //關閉容器
408         p.close();  
409     }  
410 } 
View Code

 

親測 ,發現將50行拆解為一個xlsx文件,整體的速度是最合適的。 如果1000行拆解為一個xlsx文件,速度非常慢,因為每處理一個cell的數據,都會去獲取xlsx文件對象。越到后面,xlsx文件中行數越大,數據越多,獲取到這個xlsx對象也就越來越難以忍受。

 

----------------------------------------------------------------------------------待定,之后使用多線程和對象池來提高速度------------------------------------------------------------------------------------------------


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM