下面我将从 根本原因、解决方案(针对不同场景) 和 最佳实践 三个方面,为你详细讲解如何解决和避免这个问题。
根本原因:为什么会出现乱码?
乱码的核心问题只有一个:字符编码不统一。
就是数据在写入 Excel 文件时使用的编码,和你用 Excel 打开这个文件时默认读取的编码不一致。
- Java 端的编码:在 Java 中,我们处理字符串通常使用
UTF-8编码,当我们用OutputStream写入文件时,如果指定了UTF-8,文件本身就会带有UTF-8的“签名”(BOM - Byte Order Mark)。 - Excel 端的默认编码:
- 对于
.xls(Excel 97-2003):这个格式是老式的二进制格式,它本身不依赖字符编码,它的乱码问题通常不是因为文件编码,而是因为 单元格格式设置错误(比如把数字格式的单元格用setCellValue("中文")去填充)。 - 对于
.xlsx(Excel 2007+):这个格式是基于 XML 的,XML 文件本身可以声明自己的编码,<?xml version="1.0" encoding="UTF-8"?>。微软的 Excel 在解析.xlsx文件时,有一个非常“固执”的默认行为:它默认使用UTF-8 without BOM来读取。
- 对于
冲突点:
- Java 写入时使用了
UTF-8并带上了 BOM,Excel 默认用UTF-8 without BOM去读,就可能因为多出来的 BOM 字节而解析失败,导致乱码或文件损坏。 - Java 写入时没有明确指定编码,使用了系统默认编码(Windows 上的
GBK),而 Excel 期望UTF-8,那必然会乱码。
解决方案(分场景解决)
根据你使用的库不同,解决方案也略有差异,目前主流的库有 Apache POI 和 EasyExcel。
使用 Apache POI (最经典、最全面的库)
针对 .xls (HSSFWorkbook) 格式
.xls 格式本身对 UTF-8 支持不佳,乱码通常不是编码问题,而是 字体问题,Excel 需要有能显示中文字体的环境。
解决方案: 在创建单元格样式时,明确指定一个支持中文的字体,如 "Arial Unicode MS" 或 "SimSun"。
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
// ... 其他 import
public void exportXls(HttpServletResponse response) throws IOException {
// 1. 创建工作簿
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet("测试Sheet");
// 2. 创建行和单元格
HSSFRow row = sheet.createRow(0);
HSSFCell cell = row.createCell(0);
cell.setCellValue("你好,世界!");
// 3. 【关键】创建支持中文的样式和字体
HSSFCellStyle style = workbook.createCellStyle();
HSSFFont font = workbook.createFont();
font.setFontName("SimSun"); // 使用宋体,确保系统有此字体
// font.setFontName("Arial Unicode MS"); // 或者使用这个跨平台字体
style.setFont(font);
cell.setCellStyle(style);
// 4. 设置响应头,告诉浏览器这是一个要下载的文件
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment;filename=test.xls");
// 5. 写入输出流
workbook.write(response.getOutputStream());
workbook.close();
}
针对 .xlsx (XSSFWorkbook) 格式
这是最容易出现乱码的场景,核心是 确保写入的 XML 声明是 UTF-8 without BOM。
最佳实践(推荐):
使用 SXSSFWorkbook,它是一个基于流的大数据量导出解决方案,性能更好,并且默认就能很好地处理编码问题。
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFCell;
// ... 其他 import
public void exportXlsx(HttpServletResponse response) throws IOException {
// 1. 创建 SXSSFWorkbook (内存中的行数,超过则写入磁盘)
// -1 表示不限制,全部在内存中,适合小数据量
SXSSFWorkbook workbook = new SXSSFWorkbook(-1);
SXSSFSheet sheet = workbook.createSheet("测试Sheet");
// 2. 创建行和单元格
SXSSFRow row = sheet.createRow(0);
SXSSFCell cell = row.createCell(0);
cell.setCellValue("你好,世界!This is a test.");
// 3. 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment;filename=test.xlsx");
// 4. 写入输出流
// POI 会自动处理 XML 的编码声明为 UTF-8 without BOM
workbook.write(response.getOutputStream());
// 5. 【重要】清理临时文件
workbook.dispose();
}
为什么 SXSSFWorkbook/XSSFWorkbook 能解决?
当你调用 workbook.write(outputStream) 时,POI 会将数据组装成 .xlsx 格式的 ZIP 包,在生成内部的 XML 文件时,它会正确地写入 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 这样的声明,这正是 Excel 所期望的。
使用 EasyExcel (阿里巴巴开源,更简单易用)
EasyExcel 在设计上就很好地规避了 POI 的一些坑,包括编码问题,它的 API 更简洁,对内存优化也做得很好。
解决方案:
EasyExcel 的 ExcelWriter 会自动处理编码问题,你只需要专注于业务逻辑即可。
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteTable;
import jakarta.servlet.http.HttpServletResponse; // 如果是 Jakarta EE
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
// 定义一个数据模型
@Data // 使用 Lombok
class UserData {
@ExcelProperty("姓名")
private String name;
@ExcelProperty("年龄")
private Integer age;
}
public void exportWithEasyExcel(HttpServletResponse response) throws IOException {
// 1. 准备数据
List<UserData> dataList = new ArrayList<>();
dataList.add(new UserData("张三", 25));
dataList.add(new UserData("李四", 30));
dataList.add(new UserData("王五", 28));
// 2. 设置响应头
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition", "attachment;filename=user_info.xlsx");
// 3. 【核心】EasyExcel 会自动处理编码
// 直接 write,EasyExcel 会生成正确的 UTF-8 without BOM 的 xlsx 文件
EasyExcel.write(response.getOutputStream(), UserData.class)
.sheet("用户数据")
.doWrite(dataList);
}
为什么 EasyExcel 能解决?
EasyExcel 底层虽然也基于 POI,但它对写入过程进行了高度封装和优化,在生成 Excel 文件时,它会确保 XML 声明和内容都使用 UTF-8 编码,并且避免了 BOM 的问题,所以开发者几乎不需要关心底层编码细节。
总结与最佳实践
| 场景 | 推荐库 | 核心解决方案 | 备注 |
|---|---|---|---|
导出 .xls |
Apache POI | 设置单元格样式,指定中文字体 (如 SimSun) |
老旧格式,不推荐用于新项目 |
导出 .xlsx (小数据) |
Apache POI / EasyExcel | 使用 XSSFWorkbook 或 EasyExcel,确保 UTF-8 without BOM |
POI 需要手动处理,EasyExcel 自动处理 |
导出 .xlsx (大数据) |
Apache POI (SXSSFWorkbook) / EasyExcel | 使用 SXSSFWorkbook 或 EasyExcel 的分页/分块写入 |
强烈推荐,能有效防止内存溢出 |
| Spring Boot 项目 | EasyExcel | 直接使用 EasyExcel.write(),配置简单 |
API 友好,社区活跃,与 Spring 生态集成好 |
通用 Checklist (检查清单)
如果你的 Excel 导出出了乱码,按以下步骤检查:
- 确认文件格式:是
.xls还是.xlsx?.xls-> 检查 字体 是否支持中文。.xlsx-> 检查 编码。
- 检查响应头:
Content-Type是否正确?.xls:application/vnd.ms-excel.xlsx:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Content-Disposition是否正确?attachment;filename=xxx.xlsx
- 检查编码处理:
- 如果你用 POI:确保使用的是
XSSFWorkbook或SXSSFWorkbook,而不是旧版的HSSFWorkbook(除非必须兼容旧格式)。 - 如果你用 EasyExcel:恭喜你,大概率不是编码问题,检查数据是否正确即可。
- 如果你用 POI:确保使用的是
- 检查输出流:
- 确保在
finally块或使用try-with-resources关闭workbook和outputStream,特别是SXSSFWorkbook.dispose()必须调用。
- 确保在
- 检查文件名:如果文件名是中文,最好进行 URL 编码,防止浏览器下载时文件名乱码。
String fileName = "测试文件.xlsx"; String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", " "); response.setHeader("Content-disposition", "attachment;filename=" + encodedFileName);
遵循以上指南,你就能 99% 地解决 Java 导出 Excel 的乱码问题了,对于新项目,特别是基于 Spring Boot 的,强烈推荐使用 EasyExcel,它能让你从繁琐的细节中解放出来。
