前言:這幾天遇到一個需求,需要從數據庫讀取記錄然後導出成excel表格,
在這個過程遇到很多問題,從最開始使用HSSFWorkbook到XSSFWorkbook,最後使用
SXSSFWorkbook。
先來了解下爲什麼用SXSSFWorkbook,在源碼中發現屬性名_randomAccessWindowSize,
指定內存中緩存的最大行數,他就是SXSSFWorkbook能導出大量數據關鍵因素, 具體怎
麼實現的呢,一步步查看到SXSSFSheet源碼,在創建行的方法中(加粗部分),當達到設
置的最大行數時就flushRows 操作,把當前內存中行數寫到磁盤中去
public Row createRow(int rownum) {
int maxrow = SpreadsheetVersion.EXCEL2007.getLastRowIndex();
if (rownum >= 0 && rownum <= maxrow) {
Row previousRow = rownum > 0 ? this.getRow(rownum - 1) : null;
int initialAllocationSize = 0;
if (previousRow != null) {
initialAllocationSize = previousRow.getLastCellNum();
}
if (initialAllocationSize <= 0 && this._writer.getNumberOfFlushedRows() > 0) {
initialAllocationSize = this._writer.getNumberOfCellsOfLastFlushedRow();
}
if (initialAllocationSize <= 0) {
initialAllocationSize = 10;
}
SXSSFRow newRow = new SXSSFRow(this, initialAllocationSize);
this._rows.put(new Integer(rownum), newRow);
if (this._randomAccessWindowSize >= 0 && this._rows.size() > this._randomAccessWindowSize) {
try {
this.flushRows(this._randomAccessWindowSize);
} catch (IOException var7) {
throw new RuntimeException(var7);
}
}
return newRow;
} else {
throw new IllegalArgumentException("Invalid row number (" + rownum + ") outside allowable range (0.." + maxrow + ")");
}
}
SXSSFWorkbook已經幫我們解決了一大問題了,接下來又遇到問題,我這次導出數據量在
10萬行左右,列有15列,一次性查詢所有數據的話會造成內存突然大量佔用,某些情況可能
會造成服務奔潰,繼續分段查詢數據
僞代碼
public int exportExcel(){
XSSFWorkbook xssfWb = null;
SXSSFWorkbook sxssfWorkbook = null;
SXSSFSheet sxssSheet = null;
FileOutputStream fos = null;
try {
//內存緩存最大行數
int rowMaxCache = 100;
xssfWb = new XSSFWorkbook();
//rowMaxCache(可選)不聲明默認100
sxssfWorkbook = new SXSSFWorkbook(xssfWb, rowMaxCache);
sxssSheet = (SXSSFSheet) sxssfWorkbook.createSheet("sheet標題名");
//記錄總數
int listNum = 查詢數據庫;
//查詢數據庫最大記錄數
int sourceMaxCache = 10000;
//總共查詢次數
int findNum = 0;
if (0 == (listNum % sourceMaxCache)){
findNum = listNum / sourceMaxCache;
}else {
findNum = listNum / sourceMaxCache + 1;
}
for (int j = 0; j < findNum; j++){
//mapParam作爲查詢條件限制,這裏只設置設置數據庫中的limit
mapParam.put("start",j * sourceMaxCache + 1);
mapParam.put("end",sourceMaxCache);
//導出記錄
List<Map<String,Object>> list = 查詢數據庫操作(mapParam);
//行數
int exportNum = list.size();
//列數
int columnNum = list.get(0).size();
// 首行 標題
if (0 == j){
Row row0 = sxssSheet.createRow(0);
Cell sxssC1 = row0.createCell(0);
sxssC1.setCellValue("標題");
//設置單元格樣式 setStyle
sxssC1.setCellStyle(setStyle(sxssfWorkbook));
//合併 參數1:起始行號 參數2:終止行號 參數3:起始列號 參數4:終止列號
sxssSheet.addMergedRegion(new CellRangeAddress(0, 0, 0, exportNum));
}
//第二行 表頭
if (0 == j){
Row row1 = sxssSheet.createRow(1);
for (int k = 0; k < columnNum; k++){
// Row row, int column, String value, CellStyle style
// 行 ,列號,單元格值,樣式(可選)
CellUtil.createCell(row1, 0, value, setStyle(sxssfWorkbook));
}
}
//數據
for (int i = 0; i < exportNum; i++) {
Row row = sxssSheet.createRow(j * sourceMaxCache + i + 2);
Map<String, Object> map = list.get(i);
// 數據
for (int k = 0; k < columnNum; k++){
// Row row, int column, String value, CellStyle style
// 行 ,列號,單元格值
CellUtil.createCell(row1, 0, value);
}
}
//幫助gc回收內存
list.clear();
}
//創建文件導出文件路徑
File f = new File(currStr);
try {
if (!f.exists()) {
f.createNewFile();
}
} catch (IOException e1) {
return "0";
}
fos = new FileOutputStream(f);
sxssfWorkbook.write(fos);
} catch (Exception e) {
e.printStackTrace();
return "0";
}finally{
try {
if(fos != null){
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
maven 引入包
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>3.9</version>
</dependency>
總結,代碼雖然能實現大量數據的excel表格導出,但還有優化問題,
1、時間和空間平衡;爲了解決上面內存佔用問題,我採用了分批次查詢數據庫,來減小服務
器壓力,但這樣有引出另個問題,因爲多次查詢數據庫,造成每次數據寫完excel後會有等待
數據返回時間,而且對sql查詢性能優化顯得也更爲重要。
2、樣式問題:讀者可能會奇怪我除了對錶頭和標題加入樣式,正式數據卻沒有加入樣式,
因爲在我測試過程中,對每個單元加入樣式等其他操作時,會嚴重影響性能,在一番網上查詢
資料未解決後才得以放棄,因爲樓主覺得爲了樣式而犧牲性能有些不值得(畢竟爲每個單元格
設置樣式可是行列相乘)
希望有讀者有更好的方案提出來一起交流,如果發現代碼邏輯
有什麼錯誤也請及時指出