POI報表高級操作
上篇文章已經介紹Excel可以分爲Excel2003和Excel2007兩種版本,Excel2003在POI中使用HSSF對象,一個sheet最多允許65536條數據,處理較少數據時可以使用,但是處理百萬數據時Excel2003肯定容納不了;Excel2007在POI中使用XSSF對象,最多允許一個sheet存儲1048576條數據,表示其已經可以支持百萬數據,但是在實際運行可能中還存在問題,原因是POI報表所產生的對象,單元格對象,字體對象,都不會銷燬,導致了可能存在OutOfMemoryError(OOM)內存溢出風險。
百萬數據報表導出概述
對於百萬數據Excel的導出,基本只限於討論Excel2007版本的使用方法。ApachePOI提供三種方式解決大數據量的數據導入導出:
- 用戶模式:用戶模式有許多封裝好的方法,易於操作,但創建對象太多,內存消耗巨大
- 事件模式:基於SAX(Simple API for XML)方式解析XML,是一個接口,也是一個軟件包,XML解析的一種替代方式,和DOM解析不同點在於逐行解析,不一次性將數據全部加載到內存,相當於一邊掃面一邊解析。
- SXSSF對象:是用於生成海量Excel的文件,主要藉助臨時存儲空間生成Excel
百萬數據的導出
需求分析
在互聯網時代,百萬數據經常產生,有多種多樣的原因需要數據導出
解決方式
- 1.思路分析:
使用POI的XSSFWORK對象進行導出Excel報表,是將所有的數據一次性到單元格對象,並保存到內存中,當單元格全部創建完成纔會一次性寫入到Excel進行導出。當達到百萬級別的數據導出時,隨着單元格對象的不斷創建,內存中的數據越來越多,直到OOM。POI的SXSSFWORK對象,是專門用於處理大數據量的Excel導出的。
2.原理分析:
在實例化SXSSFWork對象時,可以指定內存中所產生的POI對象數量(默認100),一旦內存對象個數達到指定數量,就將內存中的數據寫入到磁盤,就可以將內存中數據進行銷燬,以此循環直到Excel導出完成
代碼實現
還是以用戶數據爲例子
在FileUtil中添加
對之前的做了一些小改動
package com.cn.greemes.common.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.poi.excel.BigExcelWriter;
import cn.hutool.poi.excel.ExcelUtil;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 文件操作
*/
public class FileUtil {
public static final String SYS_TEM_DIR =System.getProperty("java.io.tmpdir")+ File.separator;
public void downloadExcel(List<Map<String, String>> list, HttpServletResponse response) throws IOException {
String tempPath = SYS_TEM_DIR + IdUtil.fastSimpleUUID() + ".xlsx";
File file = new File(tempPath);
BigExcelWriter writer = ExcelUtil.getBigWriter(file);
// 一次性寫出內容,使用默認樣式,強制輸出標題
writer.write(list, true);
SXSSFSheet sheet = (SXSSFSheet)writer.getSheet();
//上面需要強轉SXSSFSheet 不然沒有trackAllColumnsForAutoSizing方法
sheet.trackAllColumnsForAutoSizing();
//列寬自適應
// writer.autoSizeColumnAll();
//response爲HttpServletResponse對象
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
//test.xls是彈出下載對話框的文件名,不能爲中文,中文請自行編碼
response.setHeader("Content-Disposition", "attachment;filename=file.xlsx");
ServletOutputStream out = response.getOutputStream();
// 終止後刪除臨時文件
file.deleteOnExit();
writer.flush(out, true);
//此處記得關閉輸出Servlet流
IoUtil.close(out);
}
public void downloadExcelBySXSSF(List<Map<String, String>> list, HttpServletResponse response) throws IOException {
String tempPath = SYS_TEM_DIR + IdUtil.fastSimpleUUID() + ".xlsx";
File file = new File(tempPath);
//2.創建工作簿
SXSSFWorkbook workbook = new SXSSFWorkbook();
//3.構造sheet
Sheet sheet =workbook.createSheet();
//創建表頭
Row row = sheet.createRow(0);
Map<String,String> mapfirst = list.get(0);
String listHead = null;
AtomicInteger headersAi = new AtomicInteger();
for (String key : mapfirst.keySet()) {
Cell cell = row.createCell(headersAi.getAndIncrement());
cell.setCellValue(key);
}
AtomicInteger datasAi = new AtomicInteger(1);
Cell cell =null;
for(Map<String, String> map : list){
Row dataRow = sheet.createRow(datasAi.getAndIncrement());
int i=0;
for (String key : map.keySet()) {
Cell cell1 = dataRow.createCell(datasAi.getAndIncrement());
String value= (String)map.get(key);
cell = dataRow.createCell(i);
cell1.setCellValue(value);
i++;
}
}
String fileName = URLEncoder.encode("用戶信息.xlsx", "UTF-8");
response.setContentType("application/octet-stream");
response.setHeader("content-disposition", "attachment;filename=" + new
String(fileName.getBytes("ISO8859-1")));
response.setHeader("filename", fileName);
workbook.write(response.getOutputStream());
}
}
在controller進行配置
@ApiOperation("導出用戶數據")
@RequestMapping(value = "/export2", method = RequestMethod.GET)
@ResponseBody
public void export2(HttpServletResponse response, @RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "pageSize", defaultValue = "5") Integer pageSize,
@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum) throws UnsupportedEncodingException, IOException {
Page<MesAdmin> adminList = adminService.list(keyword, pageSize, pageNum);
List<Map<String,String>> list = new ArrayList();
//因爲只有七條數據,所以做了多次循環添加數據
for(int i=0;i<149795;i++) {
for (MesAdmin umsAdmin : adminList.getRecords()) {
Map<String, String> map = new HashMap<>(6);
DateFormat d1 = DateFormat.getDateInstance();
map.put("姓名", umsAdmin.getUsername());
map.put("郵箱", umsAdmin.getEmail());
map.put("暱稱", umsAdmin.getNickName());
map.put("備註信息", umsAdmin.getNote());
map.put("創建時間", d1.format( umsAdmin.getCreateTime()));
String loginTime ="";
if(umsAdmin.getLoginTime()!=null){
loginTime=d1.format( umsAdmin.getLoginTime());
}
map.put("最後登錄時間",loginTime );
list.add(map);
}
}
fileUtil.downloadExcelBySXSSF(list,response);
}
結束語:
本來要介紹數據的導出,可是發現百萬級別的數據導出也需要介紹一下,明天介紹數據的導出
Github地址:
github地址:https://github.com/bangbangzhou/greemes/tree/master