1.前言
在java 开发过程中, EXCEL 导出大家都有遇到过。也遇到过各种问题,在这里也稍稍整理一下,算是一个这几年来对excel 导出的一些总结
2.常见的问题
(1)excel 文字的导出目前分为两种,一种是以 xls 格式导出,其次就是以 xlsx 格式导出。
Excel版本 | 最大行数 | 最大列数 | 最大sheet数 |
2003(XLS) | 65536(2的16次方) | 256(IV,2的8次方) | 255 |
2007以上(XLSX) | 1048576(2的20次方) | 16384(XFD,2的14次方) | 无,受内存限制 |
表格中提到的受内存限制,那么就顺带一嘴, 如果服务器内存不够大, 一次性加载过多的数据, 在低版本的poi 中, 会出现内存溢出和性能问题。 所以在新的POI 版本中,例如:4.1.2 新增了一个专门处理大数据的 SXSSFWorkbook 对象, 这个实现的原理就是将数据直接写入到硬盘,在内存中存有少量的数据(创建对象的时候可以设置)。但是这个类也是存在弊端的。数据时写入到硬盘的,已经不再内存中, 即不能在对workbook 中数据进行操作。 要有针对性的选择 XSSFWorkbook 和 SXSSFWorkbook
(2)poi 升级之后会遇到的问题。
如果现在项目中所用的poi 版本为 3.XX 的版本 。升级到4.1.2 之后, 会有很多方法失效的情况。 尤其是在设置excel 的样式上。
//POI 4.1.2 设置单元格属性
CellStyle cellStyle = workbook.createCellStyle();
//设置单元格边框
cellStyle.setBorderBottom(BorderStyle.THIN);
cellStyle.setBorderTop(BorderStyle.THIN);
cellStyle.setBorderRight(BorderStyle.THIN);
cellStyle.setBorderLeft(BorderStyle.THIN);
cellStyle.setAlignment(HorizontalAlignment.CENTER); //水平居中
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER); //垂直居中
//POI 3.17 设置单元格属性
CellStyle cellStyle = wb.createCellStyle();
cellStyle.setBorderTop(CellStyle.BORDER_THIN);
cellStyle.setBorderLeft(CellStyle.BORDER_THIN);
cellStyle.setBorderBottom(CellStyle.BORDER_THIN);
cellStyle.setBorderRight(CellStyle.BORDER_THIN);
cellStyle.setAlignment(CellStyle.ALIGN_RIGHT);//水平位置
cellStyle.setVerticalAlignment(CellStyle.VERTICAL_BOTTOM);
3.如何实现一个通用的,兼容各种情况的导出工具类
之前看到的很多所谓的通用EXCEL 导出工具类。 大多需要将要合并的表头,要导出的字段,分别定义一个map。在将除了数据之外的 keyMap 和 合并表头的Map 传到工具类中进行导出。每次新增一个页面, 就需要单独写返回字段的map 和合并表头的map . 如果涉及到的表头有2行或者3行将会更不好处理。
这次我们会实现一个兼容以上这种情况的 真 · 通用导出工具类。
实现该通用EXCEL 导出工具类的前提是需要和前端做配合, 定义一个字段, 将要导出的表头用json 格式传到后台接口中。
如上图:导出一个只有单行表头的excel 表格.
点击导出的时候,需要传以下格式的数据:(供参考)
header: excel 导出的表头
field 对应数据的key , 用于获取对应的数据。
rowspan : 对应是数值表明是单行表头还是复合表头
colspan:对应的数值表明合并了多少列
columns:合并列下面对应的下级标题
还可以根据具体的需求, 加入指定的属性, 例如:dataType 属性,对指定的列进行格式化,时间格式,金额,百分比等。
[{
"name": "",
"visible": true,
"header": "序号",
"rowspan": 1
}, {
"header": "商品名称",
"name": "pName",
"field": "pName",
"visible": true
}, {
"header": "订单ID",
"name": "orderId",
"field": "orderId",
"visible": true
}, {
"header": "订单描述",
"name": "orderDesc",
"field": "orderDesc",
"visible": true
}, {
"header": "年度",
"name": "year",
"field": "year",
"visible": true
}, {
"header": "截止日期",
"name": "jzrq",
"field": "jzrq",
"visible": true
}, {
"header": "创建人",
"name": "cjr",
"field": "cjr",
"visible": true
}, {
"header": "创建日期",
"name": "cjrq",
"field": "cjrq",
"visible": true
}]
序号 | 基本信息 | 附加信息 | 备注 | |||||
名字 | 年龄 | 地址 | 电话 | 爱好 | XX | XX |
此表格对应的数据格式看下方JSON 数据
[{
"name": "",
"header": "序号",
"rowspan": 2
},
{
"name": "",
"header": "基本信息",
"colspan": 4,
"columns": [{
"header": "名字",
"name": "name",
"field": "name"
},
{
"header": "年龄",
"name": "age",
"field": "age"
},
{
"header": "地址",
"name": "add",
"field": "add"
},
{
"header": "电话",
"name": "tel",
"field": "tel"
}
]
},
{
"name": "",
"header": "附加信息",
"colspan": 3,
"columns": [{
"header": "爱好",
"name": "aih",
"field": "aih"
},
{
"header": "xx",
"name": "xx",
"field": "xx"
},
{
"header": "xx",
"name": "xx",
"field": "xx"
}
]
}
]
public class GeneralExportUtil {
//Excel 导出, 每100W数据放入一个sheet页
private static final Integer MAX_SEND = 10000000;
/**
* 构造数据, 生成符合导出的数据
* @param jsonData jQueryLogichandler 查询出来的 数据
* @param columns 前台传过来的
* @param sheetName
* @param exportId 通过uuid 生成临时文件的名称, 前台通过UUID 下载文件, 和删除文件,
*/
public static void buildDataAndExportExcel(String jsonData, String columns, String sheetName,String exportId) {
List<JSONObject> dataList = JSONObject.parseArray(jsonData, JSONObject.class);
List<JSONObject> columnList = JSONObject.parseArray(columns, JSONObject.class);
List<String> col1 = new ArrayList<>(); //excel 第一行表头名称
List<String> col2 = new ArrayList<>(); // excel 第二行表头名称
List<String> col3 = new ArrayList<>(); // excel 第三行表头名称
List<String> keyList = new ArrayList<>(); // 数据value 对应的key
List<Integer[]> combinedTitle = new ArrayList<>(); //要合并的表头集合 第一行
List<Integer[]> combinedTitleSec = new ArrayList<>(); //要合并的表头集合第二行
List<Integer[]> combinedTitleThree = new ArrayList<>(); //要合并的表头集合第三行
//表格中第一列是复选框的情况下,columnList 中的第一个元素需要移除
JSONObject firstColumn = columnList.get(0);
if (!firstColumn.containsKey("header")){
columnList.remove(0);
}
//组织excel 表头信息
int colNumIndexStart =0; // 从第一列到最后一列的下标
for (JSONObject col : columnList) {
int rowNumEnd=0; //合并行的结束
int colNumIndexEnd=0;
col1.add(col.getString("header").replace("<br>","")); //
if (col.containsKey("rowspan")){
rowNumEnd = col.getInteger("rowspan")-1;
}
if (col.containsKey("field")) {
keyList.add(col.getString("field"));
}
if (col.containsKey("colspan")){//判断是否合并行
colNumIndexEnd = colNumIndexStart+col.getInteger("colspan") - 1;
}
if (col.containsKey("columns")) {//判断是否合并列
Integer[] array = {0,rowNumEnd,colNumIndexStart,colNumIndexEnd}; // 数组用于合并单元格
combinedTitle.add(array);
//构建第二行表头
JSONArray secondRow = col.getJSONArray("columns");
for (Object secCol : secondRow) {
colNumIndexEnd=0;
JSONObject jsonObject = (JSONObject) secCol;
col2.add(jsonObject.getString("header").replace("<br>",""));
if (jsonObject.containsKey("rowspan")) {
rowNumEnd = jsonObject.getInteger("rowspan");
}else{
rowNumEnd = 1;
}
if (jsonObject.containsKey("colspan")) { //第二行合并的表头
colNumIndexEnd = colNumIndexStart+jsonObject.getInteger("colspan") - 1;
}else{
colNumIndexEnd = colNumIndexStart;
}
if (jsonObject.containsKey("field")) {
keyList.add(jsonObject.getString("field"));
}
Integer[] array2 = {1,rowNumEnd,colNumIndexStart,colNumIndexEnd};
combinedTitleSec.add(array2);
/**
* 构建第三行表头
*/
if (jsonObject.containsKey("columns")){
JSONArray secondThree = jsonObject.getJSONArray("columns");
for (Object threeCol : secondThree) {
JSONObject threeObject = (JSONObject) threeCol;
col3.add(threeObject.getString("header").replace("<br>",""));
if (threeObject.containsKey("field")) {
keyList.add(threeObject.getString("field"));
}
if (threeObject.containsKey("colspan")) { //第二行合并的表头
colNumIndexEnd = colNumIndexStart+threeObject.getInteger("colspan") - 1;
}else {
colNumIndexEnd = colNumIndexStart;
}
Integer[] array3 = {2,2,colNumIndexStart,colNumIndexEnd};
combinedTitleThree.add(array3);
colNumIndexStart++;
}
}else{
colNumIndexStart++;
}
}
}else{
if (colNumIndexStart > 0 && colNumIndexEnd == 0) {
colNumIndexEnd = colNumIndexStart;
}
Integer[] array = {0,rowNumEnd,colNumIndexStart,colNumIndexEnd};
combinedTitle.add(array);
colNumIndexStart++;
}
}
SXSSFWorkbook workbook = null;
if (col2.size() == 0) {
workbook = exportCellSingleTitle(sheetName, keyList, col1, dataList);
}else if(col3.size() == 0){
workbook = exportCellDoubleTitle(sheetName, keyList, col1, col2, dataList, combinedTitle);
}else{
workbook =exportCellThreeTitle(sheetName, keyList, col1, col2, col3, dataList, combinedTitle, combinedTitleSec,combinedTitleThree);
}
String tempPath = FxConstant.BASEPATH + "components" + File.separator + "fileio" + File.separator + "expfile";
File outDir = new File(tempPath); // 生成本地存放临时文件的路径
if (!outDir.exists()) {
outDir.mkdirs();
}
FileOutputStream os = null;
//临时文件生成
try {
os = new FileOutputStream(tempPath + File.separator + exportId+".xlsx");
workbook.write(os);
os.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
os.close();
workbook.close();
workbook.dispose();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
*
* @param sheetName sheet 页名称
* @param key_columns MAP 中对应的字段 key ,通过key 进行取值
* @param value_columns1 表头合并第一行表头名称
* @param value_columns2 第二行表头名称
* @param datalistAll 要导出的数据
* @param list3 要合并的表头 集合 例: 标题中如果有5个合并的表头, list3 的长度就为5 元素为数组, {0,1,0,0} 代表意思
* {行开始,行结束,列开始,列结束} 意为:第一列的第一行和第二行进行合并
* @return
*/
public static SXSSFWorkbook exportCellDoubleTitle(String sheetName, List<String> key_columns, List<String> value_columns1,
List<String> value_columns2, List<JSONObject> datalistAll, List<Integer[]> list3){
SXSSFWorkbook workbook = null;
Date date = new Timestamp(System.currentTimeMillis());
System.out.println("excel开始时间:"+date.getTime());
try {
//XSSFWorkbook sheets = new XSSFWorkbook();
//创建一个Excel文件
workbook = new SXSSFWorkbook(100);
List<List<JSONObject>> partition = ListUtils.partition(datalistAll, MAX_SEND);
int sheetIndex=1;
for (List<JSONObject> datalist : partition) {
//创建一个工作表
Sheet sheet = workbook.createSheet(sheetName+sheetIndex);
sheetIndex++;
//添加表头行
Row row = sheet.createRow(0);
//设置单元格格式
CellStyle cellStyle = workbook.createCellStyle();
//设置单元格边框
cellStyle.setBorderBottom(BorderStyle.THIN);
cellStyle.setBorderTop(BorderStyle.THIN);
cellStyle.setBorderRight(BorderStyle.THIN);
cellStyle.setBorderLeft(BorderStyle.THIN);
cellStyle.setAlignment(HorizontalAlignment.CENTER); //水平居中
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER); //垂直居中
//将字段赋值到合并的单元格中
CellStyle titleStyle = workbook.createCellStyle();
Font titleFont = workbook.createFont();
titleStyle.setBorderBottom(BorderStyle.THIN);
titleStyle.setBorderTop(BorderStyle.THIN);
titleStyle.setBorderRight(BorderStyle.THIN);
titleStyle.setBorderLeft(BorderStyle.THIN);
titleStyle.setAlignment(HorizontalAlignment.CENTER); //水平居中
titleStyle.setVerticalAlignment(VerticalAlignment.CENTER); //垂直居中
titleFont.setBold(true);
titleStyle.setFont(titleFont);
if (value_columns1.size()>0){
Cell cell = null;
for (int i = 0; i < value_columns1.size(); i++) {
cell = row.createCell(list3.get(i)[2]);
cell.setCellValue(value_columns1.get(i));
cell.setCellStyle(titleStyle);
}
}
int secRowCellIndex=0;
for (Integer[] index: list3){
if (index[0] == index[1] && index[2]<=index[3]){
secRowCellIndex=index[2];
break;
}
}
row = sheet.createRow(1);
for (String cellName:value_columns2) {
//添加表头内容
Cell cell = row.createCell(secRowCellIndex);
cell.setCellValue(cellName);
cell.setCellStyle(titleStyle);
sheet.setColumnWidth(secRowCellIndex,(int)((cellName.length()<=2?4:cellName.length())*2.5+0.72)*256);
secRowCellIndex++;
}
for (Integer[] arr : list3) {//合并表头
if(((arr[2]>0 ||arr[3]>0) && arr[2] != arr[3]) || (arr[0]>0 || arr[1]>0)) {
CellRangeAddress cellAddresses = new CellRangeAddress(arr[0], arr[1], arr[2], arr[3]);
sheet.addMergedRegion(cellAddresses);
RegionUtil.setBorderBottom(BorderStyle.THIN,cellAddresses,sheet);
RegionUtil.setBorderLeft(BorderStyle.THIN,cellAddresses,sheet);
RegionUtil.setBorderRight(BorderStyle.THIN,cellAddresses,sheet);
RegionUtil.setBorderTop(BorderStyle.THIN,cellAddresses,sheet);
}
}
boolean isHaveIndex = true;
//判断表格是否有序号列
if(!value_columns1.contains("序号") && !value_columns2.contains("序号")){
isHaveIndex = false;
}
//把数据添加到excel
int ii = 0;
for (int i = 0; i < datalist.size(); i++) {
row = sheet.createRow(ii + 2);
if (isHaveIndex){
//创建单元格,并设置值
int j = 1;
Cell cell = row.createCell(0);
cell.setCellValue(i+1);
cell.setCellStyle(cellStyle);
for (String s : key_columns) {
cell = row.createCell(j);
cell.setCellValue(datalist.get(i).getString(s));
cell.setCellStyle(cellStyle);
j++;
}
ii++;
}else {
//创建单元格,并设置值
int j = 0;
for (String s : key_columns) {
Cell cell = row.createCell(j);
cell.setCellValue(datalist.get(i).getString(s));
cell.setCellStyle(cellStyle);
j++;
}
ii++;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
Date date1 = new Timestamp(System.currentTimeMillis());
System.out.println("excel导出结束时间:"+(date1.getTime()-date.getTime()));
return workbook;
}
/**
* @param sheetName sheet 页名称
* @param key_columns MAP 中对应的字段 key ,通过key 进行取值
* @param value_columns 表头名称
* @param datalistAll 要导出的数据
* @return
*/
public static SXSSFWorkbook exportCellSingleTitle(String sheetName, List<String> key_columns, List<String> value_columns,
List<JSONObject> datalistAll){
SXSSFWorkbook workbook = null;
Date date = new Timestamp(System.currentTimeMillis());
System.out.println("excel开始时间:"+date.getTime());
try {
//XSSFWorkbook sheets = new XSSFWorkbook();
//创建一个Excel文件
workbook = new SXSSFWorkbook(100);
List<List<JSONObject>> partition = ListUtils.partition(datalistAll, MAX_SEND);
int sheetIndex=1;
for (List<JSONObject> datalist : partition) {
//创建一个工作表
Sheet sheet = workbook.createSheet(sheetName+sheetIndex);
sheetIndex++;
//添加表头行
Row row = sheet.createRow(0);
//设置单元格格式
CellStyle cellStyle = workbook.createCellStyle();
//设置单元格边框
cellStyle.setBorderBottom(BorderStyle.THIN);
cellStyle.setBorderTop(BorderStyle.THIN);
cellStyle.setBorderRight(BorderStyle.THIN);
cellStyle.setBorderLeft(BorderStyle.THIN);
cellStyle.setAlignment(HorizontalAlignment.CENTER); //水平居中
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER); //垂直居中
//将字段赋值到合并的单元格中
CellStyle titleStyle = workbook.createCellStyle();
Font titleFont = workbook.createFont();
titleFont.setBold(true);
titleStyle.cloneStyleFrom(cellStyle);
titleStyle.setFont(titleFont);
if (value_columns.size()>0){
Cell cell = null;
for (int i = 0; i < value_columns.size(); i++) {
cell = row.createCell(i);
cell.setCellValue(value_columns.get(i));
cell.setCellStyle(titleStyle);
//sheet.setDefaultColumnWidth(value_columns.get(i).length());
sheet.setColumnWidth(i,(int)((value_columns.get(i).length()<=2?4:value_columns.get(i).length())*2.5+0.72)*256);
}
}
//把数据添加到excel
int ii = 0;
for (int i = 0; i < datalist.size(); i++) {
row = sheet.createRow(ii + 1);
if (!value_columns.get(0).equals("序号")) {
//创建单元格,并设置值
int j = 0;
for (String s : key_columns) {
Cell cell = row.createCell(j);
cell.setCellValue(datalist.get(i).getString(s));
cell.setCellStyle(cellStyle);
j++;
}
ii++;
} else {
//创建单元格,并设置值
int j = 1;
Cell cell = row.createCell(0);
cell.setCellValue(i + 1);
cell.setCellStyle(cellStyle);
for (String s : key_columns) {
cell = row.createCell(j);
cell.setCellValue(datalist.get(i).getString(s));
cell.setCellStyle(cellStyle);
j++;
}
ii++;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
Date date1 = new Timestamp(System.currentTimeMillis());
System.out.println("excel导出结束时间:"+(date1.getTime()-date.getTime()));
return workbook;
}
/**
* 表头涉及到三行 包含合并单元格的导出情况
* @param sheetName sheet 页名称
* @param key_columns MAP 中对应的字段 key ,通过key 进行取值
* @param value_columns1 表头合并第一行表头名称
* @param value_columns2 第二行表头名称
* @param datalistAll 要导出的数据
* @param combinedTitle 要合并的表头 集合 例: 标题中如果有5个合并的表头, list3 的长度就为5 元素为数组, {0,1,0,0} 代表意思
* {行开始,行结束,列开始,列结束} 意为:第一列的第一行和第二行进行合并
* @param combinedTitleSec 要合并的表头 集合 例: 标题中如果有5个合并的表头, list3 的长度就为5 元素为数组, {0,1,0,0} 代表意思
* * {行开始,行结束,列开始,列结束} 意为:第一列的第一行和第二行进行合并
* @return
*/
public static SXSSFWorkbook exportCellThreeTitle(String sheetName, List<String> key_columns, List<String> value_columns1,
List<String> value_columns2,List<String> value_columns3,
List<JSONObject> datalistAll, List<Integer[]> combinedTitle,
List<Integer[]> combinedTitleSec,List<Integer[]> combinedTitleThree){
SXSSFWorkbook workbook = null;
Date date = new Timestamp(System.currentTimeMillis());
System.out.println("excel下载开始时间:"+date.getTime());
try {
//XSSFWorkbook sheets = new XSSFWorkbook();
//创建一个Excel文件
workbook = new SXSSFWorkbook(100);
List<List<JSONObject>> partition = ListUtils.partition(datalistAll, MAX_SEND);
int sheetIndex=1;
for (List<JSONObject> datalist : partition) {
//创建一个工作表
Sheet sheet = workbook.createSheet(sheetName + sheetIndex);
//添加表头行
Row row = sheet.createRow(0);
//设置单元格格式
CellStyle cellStyle = workbook.createCellStyle();
//设置单元格边框
cellStyle.setBorderBottom(BorderStyle.THIN);
cellStyle.setBorderTop(BorderStyle.THIN);
cellStyle.setBorderRight(BorderStyle.THIN);
cellStyle.setBorderLeft(BorderStyle.THIN);
cellStyle.setAlignment(HorizontalAlignment.CENTER); //水平居中
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER); //垂直居中
//将字段赋值到合并的单元格中
CellStyle titleStyle = workbook.createCellStyle();
Font titleFont = workbook.createFont();
titleFont.setBold(true);
titleStyle.cloneStyleFrom(cellStyle);
titleStyle.setFont(titleFont);
if (value_columns1.size() > 0) {
Cell cell = null;
for (int i = 0; i < value_columns1.size(); i++) {
cell = row.createCell(combinedTitle.get(i)[2]);
cell.setCellValue(value_columns1.get(i));
cell.setCellStyle(titleStyle);
}
}
row = sheet.createRow(1);
for (int i = 0; i < value_columns2.size(); i++) {
//添加表头内容
Cell cell = row.createCell(combinedTitleSec.get(i)[2]);
cell.setCellValue(value_columns2.get(i));
cell.setCellStyle(titleStyle);
sheet.setColumnWidth(i, (int) ((value_columns2.get(i).length() <= 2 ? 4 : value_columns2.get(i).length()) * 2.5 + 0.72) * 256);
}
row = sheet.createRow(2);
for (int i = 0; i < value_columns3.size(); i++) {
//添加表头内容
Cell cell = row.createCell(combinedTitleThree.get(i)[2]);
cell.setCellValue(value_columns3.get(i));
cell.setCellStyle(titleStyle);
sheet.setColumnWidth(i, (int) ((value_columns3.get(i).length() <= 2 ? 4 : value_columns3.get(i).length()) * 2.5 + 0.72) * 256);
}
if (sheetIndex == 1) {
combinedTitle.addAll(combinedTitleSec);//合并表头前将要合并的放到一个集合中
}
for (Integer[] arr : combinedTitle) {//合并表头
if (((arr[2] > 0 || arr[3] > 0) && arr[2] != arr[3]) || (arr[0] > 0 || arr[1] > 0)) {
CellRangeAddress cellAddresses = new CellRangeAddress(arr[0], arr[1], arr[2], arr[3]);
sheet.addMergedRegion(cellAddresses);
RegionUtil.setBorderBottom(BorderStyle.THIN, cellAddresses, sheet);
RegionUtil.setBorderLeft(BorderStyle.THIN, cellAddresses, sheet);
RegionUtil.setBorderRight(BorderStyle.THIN, cellAddresses, sheet);
RegionUtil.setBorderTop(BorderStyle.THIN, cellAddresses, sheet);
}
}
boolean isHaveIndex = true;
//判断表格是否有序号列
if(!value_columns1.contains("序号") && !value_columns2.contains("序号")){
isHaveIndex = false;
}
//把数据添加到excel
int ii = 0;
for (int i = 0; i < datalist.size(); i++) {
row = sheet.createRow(ii + 3);
//创建单元格,并设置值
if (isHaveIndex){
//创建单元格,并设置值
int j = 1;
Cell cell = row.createCell(0);
cell.setCellValue(i+1);
cell.setCellStyle(cellStyle);
for (String s : key_columns) {
cell = row.createCell(j);
cell.setCellValue(datalist.get(i).getString(s));
cell.setCellStyle(cellStyle);
j++;
}
ii++;
}else {
//创建单元格,并设置值
int j = 0;
for (String s : key_columns) {
Cell cell = row.createCell(j);
cell.setCellValue(datalist.get(i).getString(s));
cell.setCellStyle(cellStyle);
j++;
}
ii++;
}
}
sheetIndex++;
}
} catch (Exception e) {
e.printStackTrace();
}
Date date1 = new Timestamp(System.currentTimeMillis());
System.out.println("excel导出结束时间:"+(date1.getTime()-date.getTime()));
return workbook;
}
}
4.总结
图上代码兼容单行标题, 两行 和三行标题的表头格式导出. 但是此方式略显麻烦。下一章将会对此工具类进行优化, 将会采用 EasyExcel 替换掉 SXSSFWorkbook . 前端传参的方式不变. 性能方面略有提升, 将 XSSF 替换为SXSSF 之后会有明显的性能提升