背景:
前段时间,项目中需要导出报表,由于报表处理经验不是很丰富,加上需求对接阶段,没有考虑到报表导出的性能问题,当需求做完时,将相关功能打包到服务器上,当下载7000+数据时,导致服务器内存溢出,直接卡死。现网测试阶段直接打回,紧急修改问题,经过简单的度娘查询后,发现报表处理的时候,使用HSSFWorkbook和SXSSFWorkbook效率上差别很大,SXSSFWorkbook是用于处理海量数据时使用,经过本人的测试和比对后,10000条数据的效率<2s,而HSSFWorkbook效率很差劲。数据基数较小时,二者没有任何区别,当数据量大时,请老实选择SXSSFWorkbook,减少被被人喷,哈哈哈,话不多说,先上测试数据效果图。
测试结果GIF图(压缩处理过的)
从上面的GIF图片中可以明显看出,二者的效率,上面测试数据仅为500条。
Maven 依赖包如下(注意三个jar包需要同一版本,且最好是3.5以上的版本):
org.apache.poi
poi
4.0.1
org.apache.poi
poi-ooxml
4.0.1
org.apache.poi
下面给出可直接使用的工具类,
1. HSSFWorkbook的使用
此工具类未做通用处理
代码如下:
package com.lau.module.Utils;
import com.alibaba.druid.util.StringUtils;
import com.lau.module.common.domain.ExcelDomain;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/**
* @ClassName HssfExcelUtils
* @Description HSSFWorkbook处理表格工具类
* @Author Mr.Lau
* @Date 2020/05/06 19:49
* @Version 1.0
**/
public class HssfExcelUtils {
/**
* 创建表头
* @param tableNames
* @param sheet
*/
private static void createTableHeader(String[] tableNames, HSSFSheet sheet)
{
HSSFHeader header = sheet.getHeader();
HSSFRow sheetRow = sheet.createRow(0);
for(int i=0;i<tableNames.length;i++)
{
HSSFCell cell = sheetRow.createCell(i);
cell.setCellType(CellType.STRING);
cell.setCellValue(tableNames[i]);
}
}
private static void cereteTableRow(List<String> cells,short rowIndex,HSSFSheet sheet)
{
HSSFRow row = sheet.createRow(rowIndex);
for(int i = 0; i<cells.size();i++)
{
HSSFCell cell = row.createCell(i);
CellType cellType = cell.getCellType();
if(!StringUtils.equals(cellType.name(),CellType.STRING.name()))
{
cell.setCellType(CellType.STRING);
}
cell.setCellValue(cells.get(i));
// 设置自动列宽
sheet.autoSizeColumn(i);
sheet.setColumnWidth(i,sheet.getColumnWidth(i)*17/10);
}
}
private static void createExcelSheet(String[] tableNames, HSSFSheet sheet,List<ExcelDomain> list)
{
createTableHeader(tableNames,sheet);
int rowIndex = 1;
if(org.apache.commons.collections4.CollectionUtils.isNotEmpty(list))
{
for(int i=0;i<list.size();i++)
{
List<String> listRead = new ArrayList<>();
listRead.add(list.get(i).getAge());
listRead.add(list.get(i).getClazz());
listRead.add(list.get(i).getGender());
listRead.add(list.get(i).getName());
listRead.add(list.get(i).getTitle());
cereteTableRow(listRead,(short) rowIndex,sheet);
rowIndex++;
}
}
}
public static InputStream exportExcelInputStream(HSSFSheet sheet,HSSFWorkbook workbook,ByteArrayOutputStream outputStream)
{
InputStream inputStream = null ;
sheet.setGridsPrinted(true);
HSSFFooter footer = sheet.getFooter();
footer.setRight("Page"+HSSFFooter.page()+"of"+HSSFFooter.numPages());
try {
workbook.write(outputStream);
inputStream = new ByteArrayInputStream(outputStream.toByteArray());
} catch (IOException e) {
e.printStackTrace();
}
return inputStream ;
}
/**
* 生成excel 输出流
* @param workBookName
* @param tableHeaderName
* @param list
* @return
*/
public static InputStream produceExcelInputStream(String workBookName,String[] tableHeaderName,List<ExcelDomain> list)
{
InputStream inputStream = null ;
HSSFWorkbook workbook = new HSSFWorkbook();
ByteArrayOutputStream out = new ByteArrayOutputStream();
HSSFSheet sheet = workbook.createSheet();
createExcelSheet(tableHeaderName,sheet,list);
inputStream = exportExcelInputStream(sheet,workbook,out);
return inputStream ;
}
public static void produceExcel(String fileName,String[] tableHeaderName,List<ExcelDomain> list)
{
HSSFWorkbook workbook = new HSSFWorkbook();
ByteArrayOutputStream out = new ByteArrayOutputStream();
HSSFSheet sheet = workbook.createSheet();
createExcelSheet(tableHeaderName,sheet,list);
FileOutputStream fileOutputStream = null ;
try {
fileOutputStream = new FileOutputStream("./"+fileName);
workbook.write(fileOutputStream);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 设置表格样式
private static HSSFCellStyle setHeadStyle(HSSFWorkbook workbook,boolean isHeader)
{
HSSFCellStyle cellStyle = workbook.createCellStyle();
if(isHeader)
{
// 设置背景色
cellStyle.setFillForegroundColor(HSSFColor.HSSFColorPredefined.LIGHT_GREEN.getIndex());
//设置填充色
cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
//设置标题
HSSFFont font = workbook.createFont();
font.setFontHeightInPoints((short) 12);
font.setFontName("宋体");
//设置字体粗体
font.setBold(true);
cellStyle.setFont(font);
}
else
{
cellStyle.setFillForegroundColor(HSSFColor.HSSFColorPredefined.WHITE.getIndex());
}
//设置水平juzhong
cellStyle.setAlignment(HorizontalAlignment.CENTER);
//设置上下左右边框
cellStyle.setBorderBottom(BorderStyle.THIN);
cellStyle.setBorderLeft(BorderStyle.THIN);
cellStyle.setBorderRight(BorderStyle.THIN);
cellStyle.setBorderTop(BorderStyle.THIN);
return cellStyle ;
}
public static void main(String[] args)
{
ArrayList<ExcelDomain> list = new ArrayList<>();
for(int i = 0;i<500;i++)
{
ExcelDomain excelDomain = new ExcelDomain("小米" + i, i + "", "class" + i, "男", "title" + i);
list.add(excelDomain);
}
Calendar cal = Calendar.getInstance();
Date date = cal.getTime();
System.out.println("HSSFWorkbook生成excel 开始时间"+new SimpleDateFormat("yyyy/MM/dd-HH:mm:ss:SSS").format(date));
String fileName = "测试生成文件.xls";
String[] names = {"姓名","年龄","班级","性别","title"};
produceExcel(fileName,names,list);
Calendar cal1 = Calendar.getInstance();
Date date1 = cal1.getTime();
System.out.println("HSSFWorkbook生成excel 截至时间"+new SimpleDateFormat("yyyy/MM/dd-HH:mm:ss:SSS").format(date1));
}
}
说明:如果只需供前台下载,后台部分就没必要先生成excel,在读流在删除临时文件,可以直接使用com.lau.module.Utils.HssfExcelUtils#produceExcelInputStream该方法即可。
2. SXSSFWorkbook的使用
此工具类具有通用性,数据源数据只需是ArrayList list 类型即可,程序自动按照Object 字段的顺序依次解析,使数据一一对应。
package com.lau.module.Utils;
import com.lau.module.common.domain.ExcelDomain;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/**
* @ClassName SXSSFworkbookExcelUtil
* @Description 处理海量数据生成excel工具类
* @Author Mr.Lau
* @Date 2020/05/31 12:31
* @Version 1.0
**/
public class SXSSFworkbookExcelUtil {
private static CellStyle setCellStyle(SXSSFWorkbook wb,boolean isHeader)
{
CellStyle cellStyle = wb.createCellStyle();
if(isHeader)
{
cellStyle.setFillForegroundColor(HSSFColor.HSSFColorPredefined.LIGHT_GREEN.getIndex());
cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
}
//水平居中
cellStyle.setAlignment(HorizontalAlignment.CENTER);
//设置上下左右边框
cellStyle.setBorderTop(BorderStyle.THIN);
cellStyle.setBorderBottom(BorderStyle.THIN);
cellStyle.setBorderLeft(BorderStyle.THIN);
cellStyle.setBorderRight(BorderStyle.THIN);
//字体设置
Font font = wb.createFont();
font.setFontName("宋体");
font.setBold(true);
font.setFontHeightInPoints((short)11);
cellStyle.setFont(font);
//自动换行
cellStyle.setWrapText(true);
return cellStyle ;
}
private static void createExcelSheet(SXSSFWorkbook wb, List<Object> list,
int eachSheetNum ,String[] firstRowName,int columnWidth)
{
// 创建头
if(list !=null && list.size()>=0)
{
//获取分页指标
int total = list.size();
int avg = (total/eachSheetNum)+1;
if(total %eachSheetNum == 0)
{
// sheet的数量
avg = (total/eachSheetNum);
}
//样式 行首样式
CellStyle headCellStyle = setCellStyle(wb, true);
// 数据项样式
CellStyle dataCellStyle1 = setCellStyle(wb, false);
for(int m=0;m<avg ;m++)
{
SXSSFSheet sheet = wb.createSheet("报表导出" + (m + 1));
sheet.createFreezePane(0,1,0,1);
int rowNum = 0 ;
// 设置列宽
sheet = setSxssfSheet(sheet,headCellStyle,firstRowName,columnWidth,rowNum);
rowNum += 1;
//数据渲染
Object excelDomain = null ;
SXSSFRow sheetRow = null ;
SXSSFCell sheetCell = null ;
for(int n = eachSheetNum*m ;n<eachSheetNum*(m+1);n++)
{
if(n<total)
{
excelDomain = list.get(n);
sheetRow = sheet.createRow(rowNum);
sheetRow.setHeight((short)400);
// 数据项赋值处理
handleData(sheetCell,sheetRow,dataCellStyle1,excelDomain,n,firstRowName.length);
rowNum +=1 ;
// help gc
excelDomain = null;
sheetCell = null ;
sheetRow = null ;
}
}
}
}
}
private static SXSSFSheet setSxssfSheet(SXSSFSheet sheet,CellStyle cellStyle,
String[] firstRowName,int columnWidth,int sheetRowNum)
{
if(firstRowName !=null && firstRowName.length !=0)
{
SXSSFRow sheetRow = null ;
int headLength = firstRowName.length ;
for(int i =0;i<headLength;i++)
{
// 设置列宽
sheet.setColumnWidth(i,columnWidth);
if(i== 0)
{
sheetRow = sheet.createRow(sheetRowNum);
//设置行高
sheetRow.setHeight((short) 400);
}
SXSSFCell sheetCell = sheetRow.createCell(i);
sheetCell.setCellValue(firstRowName[i]);
sheetCell.setCellStyle(cellStyle);
}
}
return sheet ;
}
/**
* 列名的顺序需要和对应映射的数据类的属性顺序一致,确保数据一一对应,真实有效
* @param sheetCell
* @param sheetRow
* @param style
* @param obj
* @param n
* @param headLen
*/
private static void handleData(SXSSFCell sheetCell,SXSSFRow sheetRow,
CellStyle style,Object obj,int n,int headLen)
{
int i= 0 ;
// 利用反射技术,获取类字段属性的值
ArrayList<String> fieldList = ReflectUtils.getFieldList(obj);
while(i !=headLen)
{
sheetCell = sheetRow.createCell(i);
sheetCell.setCellStyle(style);
String value = fieldList.get(i);
sheetCell.setCellValue(value);
i++ ;
}
}
/**
* 生成输出流,供前端下载使用
* @param firstRowName 列名
* @param list 待下载数据源
* @param eachSheetNum 每张表展示数据的个数,
* @param columnWidth 列宽
* @return
*/
public static InputStream produceExcelStream(String[] firstRowName,List<Object> list,int eachSheetNum,int columnWidth)
{
SXSSFWorkbook workbook = new SXSSFWorkbook(1000);
ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream inputStream = null ;
createExcelSheet(workbook,list,eachSheetNum,firstRowName,columnWidth);
try {
workbook.write(out);
inputStream = new ByteArrayInputStream(out.toByteArray());
workbook.dispose();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(out !=null)
{
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return inputStream ;
}
/**
* 生成excel文件 文件后缀名必须为 .xlsx
* 要想生成文件后缀名为.xls文件,请使用HSSFWorkbook
* @param fileName 文件名包括文件后缀
* @param firstRowName
* @param list
* @param eachSheetNum
* @param columnWidth
*/
public static void produceExcel(String fileName,String[] firstRowName,List<Object> list,int eachSheetNum,int columnWidth)
{
SXSSFWorkbook workbook = new SXSSFWorkbook(1000);
FileOutputStream fileOutputStream = null ;
createExcelSheet(workbook,list,eachSheetNum,firstRowName,columnWidth);
try {
fileOutputStream = new FileOutputStream("./"+fileName);
workbook.write(fileOutputStream);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fileOutputStream !=null)
{
fileOutputStream.close();
}
} catch (IOException e) {
}
}
}
}
其中利用反射技术处理代码如下:
package com.lau.module.Utils;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
/**
* @ClassName ReflectUtils
* @Description TODO
* @Author l_zha
* @Date 2020/05/31 13:21
* @Version 1.0
**/
public class ReflectUtils {
/**
* @Author lau
* @Param obj 某个具体的对象
* @return java.util.ArrayList
**/
public static ArrayList<String> getFieldList (Object obj)
{
ArrayList<String> list =null ;
Field[] declaredFields = obj.getClass().getDeclaredFields();
if(declaredFields !=null && declaredFields.length !=0){
list = new ArrayList<>() ;
try {
for (Field field : declaredFields) {
field.setAccessible(true);
String value = field.get(obj).toString();
list.add(value);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return list ;
}
public HashMap<String,String> getFieldMap (Object obj)
{
HashMap<String,String> map =null ;
Field[] declaredFields = obj.getClass().getDeclaredFields();
if(declaredFields !=null && declaredFields.length !=0){
map = new HashMap<>() ;
try {
for (Field field : declaredFields) {
//字段名称
String name = field.getName();
field.setAccessible(true);
Object o = field.get(obj);
map.put(name,o.toString());
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return map ;
}
public static List<Map<String, Object>> getKeyAndValues(List<Object> objects) {
return objects.stream().map(obj -> {
Class clazz = obj.getClass();
return Arrays.stream(clazz.getDeclaredFields()).collect(Collectors.toMap(Field::getName, field -> {
Object resultObj = null;
field.setAccessible(true);
try {
resultObj = field.get(obj);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return Optional.ofNullable(resultObj).orElse(0);
}, (k1, k2) -> k2));
}).collect(Collectors.toList());
}
}
以上,结合网上资源做的总结,并将部分做成通用性代码,欢迎转载。转载需要醒目标识原文地址