本次使用POI處理xlsx文件,莫名的遇到了一個沒法逾越的問題。html
總共71個xlsx文件,單個文件最大達到50M以上,71個xls文件擺在那裏就有3-4G的大小。java
在起始處理的時候,發現本來適用於正常大小的POI處理xls程序居然多次的報錯GC outofmemory 的內存移除的問題。express
【當前情況】apache
①一個50M大小的xlsx文件,使用壓縮文件打開,能夠看到xml文件達到900M以上api
②一個50M大小以上的xlsx文件,單個工做簿,行數平均在15W行---40W之間,列數在64列左右多線程
③單方面的調整了JVM運行的 堆內存大小,調整到了4G並無什麼效果app
④程序運行起來以後,XSSFWorkbook workbook1 = new XSSFWorkbook(fileInputStream); 跑起來,CPU瞬間99%滿負荷,內存最高可使用9.79G的佔用率,僅這一個xlsx文件less
【官方解決方法】eclipse
網上關於POI處理超大文件的資料不不少,粘貼,水貼更是多如牛毛。POI官網,有一個SXSSF的XSSF擴展API插件,話說是能夠【生成/處理】【有歧義】大型電子表格。xss
可是,僅僅是支持生成而已。
若是用於操做大型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 }
----------------------------------------------------------------------------------【待續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 }
實際測試的,就是下面的文件:
文件大小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 }
親測 ,發現將50行拆解爲一個xlsx文件,總體的速度是最合適的。 若是1000行拆解爲一個xlsx文件,速度很是慢,由於每處理一個cell的數據,都會去獲取xlsx文件對象。越到後面,xlsx文件中行數越大,數據越多,獲取到這個xlsx對象也就愈來愈難以忍受。
----------------------------------------------------------------------------------待定,以後使用多線程和對象池來提升速度------------------------------------------------------------------------------------------------