工作當中遇到要讀取大數據量Excel(10萬行以上,Excel 2007),用POI方式讀取,用HSSFWorkbook讀取時,超過2萬行JVM的內存就會溢出,在網上找到原來要用XML方式逐行讀取,記錄下來,以供參考。
注意:運行環境是jdk1.6,如果要在1.5的環境中運行,要把jdk1.6中的rt.jar中javax.xml包下所有類加到運行的環境中。
下面是代碼:
package com.bill.excel;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;
public class ExcelUtil extends DefaultHandler {
private SharedStringsTable sst;
private String lastContents;
private boolean nextIsString;
private int sheetIndex = -1;
private List rowlist = new ArrayList();
private int curRow = 0;
private int curCol = 0;
/**
* 讀取第一個工作簿的入口方法
* @param path
*/
public void readOneSheet(String path) throws Exception {
OPCPackage pkg = OPCPackage.open(path);
XSSFReader r = new XSSFReader(pkg);
SharedStringsTable sst = r.getSharedStringsTable();
XMLReader parser = fetchSheetParser(sst);
InputStream sheet = r.getSheet("rId1");
InputSource sheetSource = new InputSource(sheet);
parser.parse(sheetSource);
sheet.close();
}
/**
* 讀取所有工作簿的入口方法
* @param path
* @throws Exception
*/
public void process(String path) throws Exception {
OPCPackage pkg = OPCPackage.open(path);
XSSFReader r = new XSSFReader(pkg);
SharedStringsTable sst = r.getSharedStringsTable();
XMLReader parser = fetchSheetParser(sst);
Iterator sheets = r.getSheetsData();
while (sheets.hasNext()) {
curRow = 0;
sheetIndex++;
InputStream sheet = sheets.next();
InputSource sheetSource = new InputSource(sheet);
parser.parse(sheetSource);
sheet.close();
}
}
/**
* 該方法自動被調用,每讀一行調用一次,在方法中寫自己的業務邏輯即可
* @param sheetIndex 工作簿序號
* @param curRow 處理到第幾行
* @param rowList 當前數據行的數據集合
*/
public void optRow(int sheetIndex, int curRow, List rowList) {
String temp = "";
for(String str : rowList) {
temp += str + "_";
}
System.out.println(temp);
}
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 (name.equals("c")) {
// 如果下一個元素是 SST 的索引,則將nextIsString標記爲true
String cellType = attributes.getValue("t");
if (cellType != null && cellType.equals("s")) {
nextIsString = true;
} else {
nextIsString = 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) {
}
}
// v => 單元格的值,如果單元格是字符串則v標籤的值爲該字符串在SST中的索引
// 將單元格內容加入rowlist中,在這之前先去掉字符串前後的空白符
if (name.equals("v")) {
String value = lastContents.trim();
value = value.equals("") ? " " : value;
rowlist.add(curCol, value);
curCol++;
} else {
// 如果標籤名稱爲 row ,這說明已到行尾,調用 optRows() 方法
if (name.equals("row")) {
optRow(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);
}
}