標籤:cto 元素 tor void 工作 openxml XML lld contents
使用POI能夠匯出大資料保證記憶體不溢出的一個重要原因是SXSSFWorkbook產生的EXCEL為2007版本,修改EXCEL2007檔案尾碼為ZIP開啟可以看到,每一個Sheet都是一個xml檔案,儲存格格式和儲存格座標均用標籤表示。直接使用SXSSFWorkbook來到匯出EXCEL本身就是POI為了大資料量匯出而量身定製的,所以匯出可以直接使用SXSSFWorkbook方式。
為了保險起見可以採用多Sheet的方式保證記憶體不溢出。需要注意的是Sheet名稱不能重複;下載的時候需要定義好返回頭。
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
匯出EXCEL較為簡單,建立Workbook對象和Sheet對象往裡塞值就行了。但是匯入讀取EXCEL的時候SXSSFWorkbook沒有讀取檔案流的方法,只能使用XSSFWorkbook來讀取,幾千條資料可能就記憶體溢出了。
這時候就要使用OPCPackage
public static OPCPackage open(java.io.InputStream in) throws InvalidFormatException, java.io.IOExceptionOpen a package. Note - uses quite a bit more memory than open(String), which doesn‘t need to hold the whole zip file in memory, and can take advantage of native methodsParameters: in - The InputStream to read the package fromReturns: A PackageBase objectThrows: InvalidFormatException java.io.IOException
POI給出的API表示使用OPCPackage不需要將檔案完全讀取到記憶體中。
調用方法
File file = uploadFile.getFile();InputStream is = new FileInputStream(file);excelReader.readInputStream(is);excelReader.process();
ExcelReader.java
/** * 抽象Excel2007讀取器,excel2007的底層資料結構是xml檔案,採用SAX的事件驅動的方法解析 * xml,需要繼承DefaultHandler,在遇到檔案內容時,事件會觸發,這種做法可以大大降低 * 記憶體的耗費,特別使用於大資料量的檔案。 * */public class Excel2007Reader extends DefaultHandler { //共用字串表 private SharedStringsTable sst; //上一次的內容 private String lastContents; private boolean nextIsString; private int sheetIndex = -1; private List<String> rowlist = new ArrayList<String>(); //當前行 private int curRow = 0; //當前列 private int curCol = 0; //日期標誌 private boolean dateFlag; //數字標誌 private boolean numberFlag; private boolean isTElement; private IRowReader rowReader; public void setRowReader(IRowReader rowReader){ this.rowReader = rowReader; } /**只遍曆一個試算表,其中sheetId為要遍曆的sheet索引,從1開始,1-3 * @param filename * @param sheetId * @throws Exception */ public void processOneSheet(String filename,int sheetId) throws Exception { OPCPackage pkg = OPCPackage.open(filename); XSSFReader r = new XSSFReader(pkg); SharedStringsTable sst = r.getSharedStringsTable(); XMLReader parser = fetchSheetParser(sst); // 根據 rId# 或 rSheet# 尋找sheet InputStream sheet2 = r.getSheet("rId"+sheetId); sheetIndex++; InputSource sheetSource = new InputSource(sheet2); parser.parse(sheetSource); sheet2.close(); } /** * 遍曆活頁簿中所有的試算表 * @param filename * @throws Exception */ public void process(String filename) throws Exception { OPCPackage pkg = OPCPackage.open(filename); XSSFReader r = new XSSFReader(pkg); SharedStringsTable sst = r.getSharedStringsTable(); XMLReader parser = fetchSheetParser(sst); Iterator<InputStream> sheets = r.getSheetsData(); while (sheets.hasNext()) { curRow = 0; sheetIndex++; InputStream sheet = sheets.next(); InputSource sheetSource = new InputSource(sheet); parser.parse(sheetSource); sheet.close(); } } public XMLReader fetchSheetParser(SharedStringsTable sst) throws SAXException { XMLReader parser = XMLReaderFactory .createXMLReader("org.apache.xerces.parsers.SAXParser"); this.sst = sst; parser.setContentHandler(this); return parser; } public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { // c => 儲存格 if ("c".equals(name)) { // 如果下一個元素是 SST 的索引,則將nextIsString標記為true String cellType = attributes.getValue("t"); if ("s".equals(cellType)) { nextIsString = true; } else { nextIsString = false; } //日期格式 String cellDateType = attributes.getValue("s"); if ("1".equals(cellDateType)){ dateFlag = true; } else { dateFlag = false; } String cellNumberType = attributes.getValue("s"); if("2".equals(cellNumberType)){ numberFlag = true; } else { numberFlag = false; } } //當元素為t時 if("t".equals(name)){ isTElement = true; } else { isTElement = false; } // 置空 lastContents = ""; } public void endElement(String uri, String localName, String name) throws SAXException { // 根據SST的索引值的到儲存格的真正要儲存的字串 // 這時characters()方法可能會被調用多次 if (nextIsString) { try { int idx = Integer.parseInt(lastContents); lastContents = new XSSFRichTextString(sst.getEntryAt(idx)) .toString(); } catch (Exception e) { } } //t元素也包含字串 if(isTElement){ String value = lastContents.trim(); rowlist.add(curCol, value); curCol++; isTElement = false; // v => 儲存格的值,如果儲存格是字串則v標籤的值為該字串在SST中的索引 // 將儲存格內容加入rowlist中,在這之前先去掉字串前後的空白符 } else if ("v".equals(name)) { String value = lastContents.trim(); value = value.equals("")?" ":value; //日期格式處理 if(dateFlag){ Date date = HSSFDateUtil.getJavaDate(Double.valueOf(value)); SimpleDateFormat dateFormat = new SimpleDateFormat( "dd/MM/yyyy"); value = dateFormat.format(date); } //數字類型處理 if(numberFlag){ BigDecimal bd = new BigDecimal(value); value = bd.setScale(3,BigDecimal.ROUND_UP).toString(); } rowlist.add(curCol, value); curCol++; }else { //如果標籤名稱為 row ,這說明已到行尾,調用 optRows() 方法 if (name.equals("row")) { rowReader.getRows(sheetIndex,curRow,rowlist); rowlist.clear(); curRow++; curCol = 0; } } } public void characters(char[] ch, int start, int length) throws SAXException { //得到儲存格內容的值 lastContents += new String(ch, start, length); }}
POI實現大資料EXCLE匯入匯出,解決記憶體溢出問題