杰瑞科技汇

Java导出Excel乱码,如何解决编码问题?

下面我将从 根本原因解决方案(针对不同场景)最佳实践 三个方面,为你详细讲解如何解决和避免这个问题。


根本原因:为什么会出现乱码?

乱码的核心问题只有一个:字符编码不统一

就是数据在写入 Excel 文件时使用的编码,和你用 Excel 打开这个文件时默认读取的编码不一致。

  1. Java 端的编码:在 Java 中,我们处理字符串通常使用 UTF-8 编码,当我们用 OutputStream 写入文件时,如果指定了 UTF-8,文件本身就会带有 UTF-8 的“签名”(BOM - Byte Order Mark)。
  2. 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 POIEasyExcel

使用 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 使用 XSSFWorkbookEasyExcel,确保 UTF-8 without BOM POI 需要手动处理,EasyExcel 自动处理
导出 .xlsx (大数据) Apache POI (SXSSFWorkbook) / EasyExcel 使用 SXSSFWorkbook 或 EasyExcel 的分页/分块写入 强烈推荐,能有效防止内存溢出
Spring Boot 项目 EasyExcel 直接使用 EasyExcel.write(),配置简单 API 友好,社区活跃,与 Spring 生态集成好

通用 Checklist (检查清单)

如果你的 Excel 导出出了乱码,按以下步骤检查:

  1. 确认文件格式:是 .xls 还是 .xlsx
    • .xls -> 检查 字体 是否支持中文。
    • .xlsx -> 检查 编码
  2. 检查响应头
    • Content-Type 是否正确?
      • .xls: application/vnd.ms-excel
      • .xlsx: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
    • Content-Disposition 是否正确?attachment;filename=xxx.xlsx
  3. 检查编码处理
    • 如果你用 POI:确保使用的是 XSSFWorkbookSXSSFWorkbook,而不是旧版的 HSSFWorkbook(除非必须兼容旧格式)。
    • 如果你用 EasyExcel:恭喜你,大概率不是编码问题,检查数据是否正确即可。
  4. 检查输出流
    • 确保在 finally 块或使用 try-with-resources 关闭 workbookoutputStream,特别是 SXSSFWorkbook.dispose() 必须调用。
  5. 检查文件名:如果文件名是中文,最好进行 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,它能让你从繁琐的细节中解放出来。

分享:
扫描分享到社交APP
上一篇
下一篇