apache poi 介绍
Apache POI 是一个开源的 Java 库,可以用来读写 Microsoft Office 文档,包括 Word、Excel、PowerPoint 等各种格式。
maven依赖
用的是最新的版本 5.3.0
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.3.0</version>
</dependency>
技术介绍
在导出的过程中使用到了SXSSFWorkbook
,是从POI 3.8版本开始,提供的一种基于XSSF的低内存占用的API,能有效的避免导出数量过大时,OOM问题。
引用官方的介绍,简单概括就是:
SXSSF是对XSSF的一种流式扩展,特点和原理是采用了滑动窗口的机制,低内存占用,主要用于数据量非常大的电子表格而虚拟机堆有限的情况。
SXSSFWorkbook.DEFAULT_WINDOW_SIZE
默认值是100,表示在内存中最多存在100个Row对象,当写第101个Row对象的时候就会把第1个Row对象以XML格式写入C:\Users\wange\AppData\Local\Temp路径下的临时文件中,后面的以此类推,始终保持内存中最多存在100个Row对象。
SXSSFWorkbook默认使用内联字符串而不是共享字符串表(SharedStringsTable)。启用共享字符串时,文档中的所有唯一字符串都必须保存在内存中,因此XSSF会占用更多的内存。
与XSSF的对比,在一个时间点上,只可以访问一定数量的Row;不再支持Sheet.clone();不再支持公式的求值。但是除了滑动窗口,其余的EXCLE操作仍然使用的是XSSF的API。
导出流程
1.创建Workbook对象
2.创建sheet工作表
3.穿件Row行
4.创建Cell单元格
5.写入单元格数据,一个格式
6.导出字节流
代码
直接看代码
FileExportController.java
@RestController
public class FileController {
@Autowired
private FileExport fileExport;
/**
* excel导出接口
* 这里自定义了一些导出数据
**/
@GetMapping("export")
public ResponseEntity<InputStreamResource> export(){
List<Object[]> dataList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Object[] objects = new Object[10];
objects[0] = "1000"+i;
objects[1] = "学生"+(i+1);
objects[2] = "实验班";
objects[3] = i*5;
dataList.add(objects);
}
ExportExcelData of = ExportExcelData.of("导出测试",
new String[]{"学号","姓名","班级","成绩"},dataList);
ByteArrayOutputStream outputStream = (ByteArrayOutputStream) fileExport.excelExport(of,new ExportDataConsumer());
byte[] bytes = outputStream.toByteArray();
InputStreamResource resource = new InputStreamResource(new ByteArrayInputStream(bytes));
return ResponseEntity.ok()
.header("Content-Disposition", "attachment; filename=test-export.xlsx")
.body(resource);
}
}
导出数据实体类,导出的数据封装成一个javabean实体
public class ExportExcelData {
//显示的导出表的标题
private final String title;
//导出表的列名
private final String[] colName;
//列的数量
private final Integer colNum;
//列中的数据
private final List<Object[]> dataList;
public ExportExcelData(String title,String[] colName,List<Object[]> dataList){
this.title = title;
this.colName = colName;
this.dataList = dataList;
colNum = colName.length;
}
public static ExportExcelData of(String title,String[] colName,List<Object[]> dataList){
return new ExportExcelData(title,colName,dataList);
}
public static ExportExcelData of(String title,String[] colName){
return new ExportExcelData(title,colName,null);
}
public String getTitle() {
return title;
}
public String[] getColName() {
return colName;
}
public Integer getColNum() {
return colNum;
}
public List<Object[]> getDataList() {
return dataList;
}
}
导出数据apache poi 核心逻辑类
在方法个编写的时候增加了一个Consmer 参数,可以在调用方法时,自定义导出数据的格式。如单元格合并导出,单元格显示图片等等。
public interface FileExport {
default OutputStream excelExport(ExportExcelData exportExcelData, Consumer<Map<String,Object>> function){
throw new RuntimeException("未实现方法");
};
}
接口实现
public class FileExportService implements FileExport {
private final short titleHeight = 20 * 30;
/**
* 让列宽随着导出的列长自动适应
* @param sheet
*/
private void columnWidthAdaptive(Sheet sheet, int columnNum, CellStyle cellStyle) {
for (int colNum = 0; colNum < columnNum; colNum++) {
int columnWidth = sheet.getColumnWidth(colNum) / 256;
for (int rowNum = 0; rowNum <= sheet.getLastRowNum(); rowNum++) {
Row currentRow;
//当前行未被使用过
if (sheet.getRow(rowNum) == null) {
currentRow = sheet.createRow(rowNum);
} else {
currentRow = sheet.getRow(rowNum);
}
Cell currentCell;
if (currentRow.getCell(colNum) != null) {
currentCell = currentRow.getCell(colNum);
} else {
currentCell = currentRow.createCell(colNum);
}
currentCell.setCellStyle(cellStyle);
int length = currentCell.getStringCellValue().getBytes().length;
if (columnWidth < length) {
columnWidth = length;
}
}
sheet.setColumnWidth(colNum, (columnWidth + 4) * 256);
}
}
public void setRegionStyle(Sheet sheet, CellRangeAddress range, CellStyle cs) {
for (int i = range.getFirstRow(); i <= range.getLastRow(); i++) {
Row row = sheet.getRow(i);
if (row == null){
row = sheet.createRow(i);
}
for (int j = range.getFirstColumn(); j <= range.getLastColumn(); j++) {
Cell cell = row.getCell(j);
if (cell == null){
cell = row.createCell(j);
}
cell.setCellStyle(cs);
}
}
}
@Override
public OutputStream excelExport(ExportExcelData exportExcelData, Consumer<Map<String,Object>> consumer) {
String[] colName = exportExcelData.getColName();
Integer colNum = exportExcelData.getColNum();
OutputStream outputStream = new ByteArrayOutputStream();
//创建工作表
SXSSFWorkbook workbook = new SXSSFWorkbook();
SXSSFSheet sheet = workbook.createSheet(exportExcelData.getTitle());
//标题单元格样式
Font titleFont = ExcelCellStyle.fontStyle(workbook, (short) 12,true);
CellStyle titleCellStyle = ExcelCellStyle.columnStyle(workbook, titleFont);
//写入标题单元格数据
SXSSFRow rowTitle = sheet.createRow(0);
SXSSFCell cellTitle = rowTitle.createCell(0, CellType.STRING);
cellTitle.setCellValue(exportExcelData.getTitle());
rowTitle.setHeight(titleHeight);
//合并标题单元格
CellRangeAddress cellAddresses = new CellRangeAddress(0, 1, 0, (colNum - 1));
sheet.addMergedRegion(cellAddresses);
setRegionStyle(sheet,cellAddresses,titleCellStyle);
//定义列头数据
SXSSFRow rowTow = sheet.createRow(2);// 在索引2的位置创建行(最顶端的行开始的第二行)
rowTow.setHeight(titleHeight); //设置高度
// 设置单元格样式
Font rowTowFont = ExcelCellStyle.fontStyle(workbook, (short) 11,false);
CellStyle rowTowCellStyle = ExcelCellStyle.columnStyle(workbook, rowTowFont);
// 将列头设置到sheet的单元格中
for (int n = 0; n < colNum; n++) {
SXSSFCell cell = rowTow.createCell(n);
cell.setCellType(CellType.STRING); //设置列头单元格的数据类型
HSSFRichTextString text = new HSSFRichTextString(colName[n]);
cell.setCellValue(text); //设置列头单元格的值
cell.setCellStyle(rowTowCellStyle); //设置列头单元格样式
}
//数据写入表格
List<Object[]> dataList = exportExcelData.getDataList();
Map<String, Object> map = new HashMap<String, Object>() {{
put("obj", dataList);put("workbook",workbook);put("sheet",sheet);
}};
consumer.accept(map);
//让列宽随着导出的列长自动适应 并这是样式
CellStyle cellStyle = ExcelCellStyle.defaultCellStyle(workbook);
columnWidthAdaptive(sheet,colNum,cellStyle);
try {
workbook.write(outputStream);
workbook.close();
} catch (IOException e) {
e.printStackTrace();
}
return outputStream;
}
}
导出书记写入默认Consumer实现
public class ExportDataConsumer implements Consumer<Map<String,Object>> {
@Override
public void accept(Map<String, Object> stringObjectMap) {
Sheet sheet = (Sheet) stringObjectMap.get("sheet");
List<Object[]> dataList = (List<Object[]>) stringObjectMap.get("obj");
for (int i = 0; i < dataList.size(); i++) {
Object[] objects = dataList.get(i);
Row row = sheet.createRow(i+3);//创建所需的行数
row.setHeight((short) (25 * 20)); //设置高度
for (int j = 0; j < objects.length; j++) {
Cell cell = row.createCell(j, CellType.STRING);
if (objects[j] != null) {
cell.setCellValue(objects[j].toString()); //设置单元格的值
}
}
}
}
}
导出文件效果
总结
综上就是一个简单的数据导出excel接口逻辑,当前的逻辑比较简单,如需要复杂的导出数据以及格式(如单元格合并,单元格插入图片等等),还需自己实现。