Apache POI Excel 导出样式美化实战指南
一、概述
使用 Apache POI 导出 Excel 时,默认样式非常简陋(无边框、无背景色、列宽不自适应)。在实际项目中,导出给用户的 Excel 文件需要具备良好的可读性,包括表头美化、数据对齐、边框、列宽自适应等。
本文以XSSFWorkbook(.xlsx 格式)为例,系统介绍 POI 中样式相关 API 的使用方法。
二、POI 样式体系结构
Workbook(工作簿) ├── Font(字体)— 控制文字样式 ├── CellStyle(单元格样式)— 控制单元格外观 ├── Sheet(工作表) │ ├── Row(行)— 控制行高 │ │ └── Cell(单元格)— 绑定 CellStyle │ └── 列宽设置
关键关系:
Font被CellStyle引用CellStyle被Cell引用- 一个
CellStyle可以被多个Cell共用(推荐复用,减少内存开销)
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
三、核心 API 详解
3.1 字体(Font)
Fontfont=workbook.createFont();font.setBold(true);// 加粗font.setItalic(true);// 斜体font.setFontHeightInPoints((short)12);// 字号(磅)font.setFontName("微软雅黑");// 字体名称font.setColor(IndexedColors.WHITE.getIndex());// 字体颜色font.setUnderline(Font.U_SINGLE);// 下划线font.setStrikeout(true);// 删除线3.2 单元格样式(CellStyle)
背景填充
CellStylestyle=workbook.createCellStyle();// 设置背景色(必须先设置前景色,再设置填充模式)style.setFillForegroundColor(IndexedColors.ROYAL_BLUE.getIndex());style.setFillPattern(FillPatternType.SOLID_FOREGROUND);常用颜色:
| 颜色常量 | 效果 |
|---|---|
IndexedColors.ROYAL_BLUE | 深蓝色(适合表头) |
IndexedColors.LIGHT_BLUE | 浅蓝色 |
IndexedColors.GREY_25_PERCENT | 浅灰色(适合交替行) |
IndexedColors.LIGHT_YELLOW | 浅黄色 |
IndexedColors.WHITE | 白色 |
IndexedColors.RED | 红色(适合错误标记) |
对齐方式
// 水平对齐style.setAlignment(HorizontalAlignment.CENTER);// 居中style.setAlignment(HorizontalAlignment.LEFT);// 左对齐style.setAlignment(HorizontalAlignment.RIGHT);// 右对齐// 垂直对齐style.setVerticalAlignment(VerticalAlignment.CENTER);// 垂直居中style.setVerticalAlignment(VerticalAlignment.TOP);// 顶部对齐style.setVerticalAlignment(VerticalAlignment.BOTTOM);// 底部对齐边框
// 四边边框(细线)style.setBorderTop(BorderStyle.THIN);style.setBorderBottom(BorderStyle.THIN);style.setBorderLeft(BorderStyle.THIN);style.setBorderRight(BorderStyle.THIN);// 边框颜色(可选,默认黑色)style.setTopBorderColor(IndexedColors.BLACK.getIndex());style.setBottomBorderColor(IndexedColors.BLACK.getIndex());style.setLeftBorderColor(IndexedColors.BLACK.getIndex());style.setRightBorderColor(IndexedColors.BLACK.getIndex());边框样式:
| 常量 | 效果 |
|---|---|
BorderStyle.THIN | 细线(最常用) |
BorderStyle.MEDIUM | 中等粗线 |
BorderStyle.THICK | 粗线 |
BorderStyle.DASHED | 虚线 |
BorderStyle.DOTTED | 点线 |
BorderStyle.NONE | 无边框 |
自动换行
style.setWrapText(true);// 内容超出列宽时自动换行绑定字体
style.setFont(font);// 将 Font 对象绑定到 CellStyle3.3 行高
Rowrow=sheet.createRow(0);row.setHeightInPoints(20);// 行高20磅// 或row.setHeight((short)(20*20));// 单位是 1/20 磅3.4 列宽
// 固定列宽(单位是 1/256 个字符宽度)sheet.setColumnWidth(0,5000);// 第0列宽度约20个字符// 自适应列宽(根据内容自动调整)sheet.autoSizeColumn(0);// 自适应 + 额外余量(推荐)sheet.autoSizeColumn(0);intwidth=sheet.getColumnWidth(0);sheet.setColumnWidth(0,Math.max(width+512,4000));// 至少4000宽度注意:autoSizeColumn对中文支持不够好,可能偏窄,建议加 512~1024 的余量。
四、完整示例
publicStringexportEmployee(List<EmployeeExportDto>dataList){XSSFWorkbookworkbook=newXSSFWorkbook();Sheetsheet=workbook.createSheet("员工信息");// ===== 1. 定义表头样式 =====FontheaderFont=workbook.createFont();headerFont.setBold(true);headerFont.setColor(IndexedColors.WHITE.getIndex());headerFont.setFontHeightInPoints((short)11);headerFont.setFontName("微软雅黑");CellStyleheaderStyle=workbook.createCellStyle();headerStyle.setFont(headerFont);headerStyle.setFillForegroundColor(IndexedColors.ROYAL_BLUE.getIndex());headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);headerStyle.setAlignment(HorizontalAlignment.CENTER);headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);headerStyle.setBorderTop(BorderStyle.THIN);headerStyle.setBorderBottom(BorderStyle.THIN);headerStyle.setBorderLeft(BorderStyle.THIN);headerStyle.setBorderRight(BorderStyle.THIN);// ===== 2. 定义数据行样式 =====FontdataFont=workbook.createFont();dataFont.setFontHeightInPoints((short)10);dataFont.setFontName("微软雅黑");CellStyledataStyle=workbook.createCellStyle();dataStyle.setFont(dataFont);dataStyle.setAlignment(HorizontalAlignment.LEFT);dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);dataStyle.setWrapText(true);dataStyle.setBorderTop(BorderStyle.THIN);dataStyle.setBorderBottom(BorderStyle.THIN);dataStyle.setBorderLeft(BorderStyle.THIN);dataStyle.setBorderRight(BorderStyle.THIN);// ===== 3. 创建表头行 =====String[]headers={"工号","姓名","部门","手机号","状态","操作人","创建时间"};RowheaderRow=sheet.createRow(0);headerRow.setHeightInPoints(22);for(inti=0;i<headers.length;i++){Cellcell=headerRow.createCell(i);cell.setCellValue(headers[i]);cell.setCellStyle(headerStyle);}// ===== 4. 填充数据行 =====SimpleDateFormatsdf=newSimpleDateFormat("yyyy-MM-dd HH:mm:ss");for(inti=0;i<dataList.size();i++){EmployeeExportDtodto=dataList.get(i);Rowrow=sheet.createRow(i+1);row.setHeightInPoints(18);createStyledCell(row,0,dto.getStaffNo(),dataStyle);createStyledCell(row,1,dto.getStaffName(),dataStyle);createStyledCell(row,2,dto.getDeptName(),dataStyle);createStyledCell(row,3,dto.getPhone(),dataStyle);createStyledCell(row,4,dto.getStatusName(),dataStyle);createStyledCell(row,5,dto.getOperatorName(),dataStyle);createStyledCell(row,6,dto.getCreateTime()!=null?sdf.format(dto.getCreateTime()):"",dataStyle);}// ===== 5. 设置列宽自适应 =====for(inti=0;i<headers.length;i++){sheet.autoSizeColumn(i);intcurrentWidth=sheet.getColumnWidth(i);sheet.setColumnWidth(i,Math.max(currentWidth+512,4000));}// ===== 6. 写入文件并上传 =====// ...省略文件操作代码returnossUrl;}/** * 创建带样式的单元格. */privatevoidcreateStyledCell(Rowrow,intcolIndex,Stringvalue,CellStylestyle){Cellcell=row.createCell(colIndex);cell.setCellValue(value!=null?value:"");cell.setCellStyle(style);}五、样式复用原则
5.1 为什么要复用 CellStyle
POI 中每个 Workbook 最多支持约 64000 个 CellStyle。如果每个 Cell 都 new 一个 CellStyle,数据量大时会报错:
java.lang.IllegalStateException: The maximum number of Cell Styles was exceeded.5.2 正确做法
// 正确:在循环外创建样式,循环内复用CellStyledataStyle=workbook.createCellStyle();// ... 设置样式属性for(inti=0;i<dataList.size();i++){Rowrow=sheet.createRow(i+1);Cellcell=row.createCell(0);cell.setCellStyle(dataStyle);// 复用同一个样式对象}// 错误:在循环内创建样式for(inti=0;i<dataList.size();i++){CellStylestyle=workbook.createCellStyle();// 每行创建新样式,浪费资源// ...}5.3 需要多种样式时
如果需要交替行背景色等不同样式,预先创建有限个样式对象:
// 创建两种数据行样式(白色背景 + 浅灰背景)CellStyleevenStyle=createDataStyle(workbook,IndexedColors.WHITE);CellStyleoddStyle=createDataStyle(workbook,IndexedColors.GREY_25_PERCENT);for(inti=0;i<dataList.size();i++){CellStylestyle=(i%2==0)?evenStyle:oddStyle;// 使用对应样式}六、常见样式模板
6.1 标准表头(蓝底白字)
FontheaderFont=workbook.createFont();headerFont.setBold(true);headerFont.setColor(IndexedColors.WHITE.getIndex());headerFont.setFontHeightInPoints((short)11);CellStyleheaderStyle=workbook.createCellStyle();headerStyle.setFont(headerFont);headerStyle.setFillForegroundColor(IndexedColors.ROYAL_BLUE.getIndex());headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);headerStyle.setAlignment(HorizontalAlignment.CENTER);headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);headerStyle.setBorderTop(BorderStyle.THIN);headerStyle.setBorderBottom(BorderStyle.THIN);headerStyle.setBorderLeft(BorderStyle.THIN);headerStyle.setBorderRight(BorderStyle.THIN);6.2 标准数据行(左对齐+换行+边框)
FontdataFont=workbook.createFont();dataFont.setFontHeightInPoints((short)10);CellStyledataStyle=workbook.createCellStyle();dataStyle.setFont(dataFont);dataStyle.setAlignment(HorizontalAlignment.LEFT);dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);dataStyle.setWrapText(true);dataStyle.setBorderTop(BorderStyle.THIN);dataStyle.setBorderBottom(BorderStyle.THIN);dataStyle.setBorderLeft(BorderStyle.THIN);dataStyle.setBorderRight(BorderStyle.THIN);6.3 数字列(右对齐)
CellStylenumberStyle=workbook.createCellStyle();numberStyle.cloneStyleFrom(dataStyle);// 基于数据行样式克隆numberStyle.setAlignment(HorizontalAlignment.RIGHT);// 可选:设置数字格式DataFormatformat=workbook.createDataFormat();numberStyle.setDataFormat(format.getFormat("#,##0.00"));6.4 错误标记行(红色字体)
FonterrorFont=workbook.createFont();errorFont.setColor(IndexedColors.RED.getIndex());errorFont.setFontHeightInPoints((short)10);CellStyleerrorStyle=workbook.createCellStyle();errorStyle.setFont(errorFont);errorStyle.setAlignment(HorizontalAlignment.LEFT);errorStyle.setBorderTop(BorderStyle.THIN);errorStyle.setBorderBottom(BorderStyle.THIN);errorStyle.setBorderLeft(BorderStyle.THIN);errorStyle.setBorderRight(BorderStyle.THIN);七、列宽自适应的坑
7.1 中文字符宽度不准
autoSizeColumn对中文计算宽度偏窄,因为 POI 默认按英文字符宽度计算。
解决:自适应后加余量
sheet.autoSizeColumn(i);intwidth=sheet.getColumnWidth(i);sheet.setColumnWidth(i,(int)(width*1.2));// 增加20%7.2 大数据量性能问题
autoSizeColumn需要遍历该列所有行来计算最大宽度,数据量大时很慢。
解决:数据量 > 1万行时改用固定列宽
if(dataList.size()>10000){// 固定列宽int[]columnWidths={4000,5000,6000,5000,4000,5000,6000};for(inti=0;i<columnWidths.length;i++){sheet.setColumnWidth(i,columnWidths[i]);}}else{// 自适应for(inti=0;i<headers.length;i++){sheet.autoSizeColumn(i);sheet.setColumnWidth(i,Math.max(sheet.getColumnWidth(i)+512,4000));}}八、最佳实践清单
- 样式对象在循环外创建:避免超出 64000 个 CellStyle 限制
- 表头和数据行使用不同样式:表头加粗+背景色,数据行左对齐+换行
- 所有单元格设置边框:提升可读性
- 表头行高适当加大:建议 20-22pt
- 数据行开启自动换行:防止长文本被截断
- 列宽自适应后加余量:中文内容需要额外 512-1024 的宽度
- null 值转空字符串:避免 Cell 显示 “null”
- 封装 createStyledCell 方法:减少重复代码
- 数字列右对齐:符合阅读习惯
- 大数据量用固定列宽:autoSizeColumn 性能差