杰瑞科技汇

Java导出Excel数据,如何实现?

主流方案对比

特性 Apache POI EasyExcel JXL (已不推荐)
维护方 Apache 软件基金会 阿里巴巴 -
核心优势 功能最强大,支持所有 Excel 版本和复杂格式 内存占用极低,性能好,解决了 POI 的 OOM 问题 简单易用,但已停止更新多年
主要缺点 大数据量时容易产生 OOM (内存溢出) 对于极其复杂的样式和图表支持不如 POI 功能有限,不支持 .xlsx 格式,已过时
适用场景 需要处理复杂 Excel 样式、图表,或数据量可控的场景 大数据量导出(万行以上),追求高性能和低内存占用的场景 旧项目维护,或非常简单的导出需求(不推荐新项目使用)
  • 新项目首选EasyExcel,它在绝大多数场景下都能完美胜任,并且解决了 POI 的最大痛点。
  • 需要复杂功能:如果必须处理复杂的样式、公式、图表等,且数据量不大,可以使用 Apache POI
  • 避免使用JXL,除非你维护的是非常古老的代码。

使用 EasyExcel (强烈推荐)

EasyExcel 是阿里巴巴开源的一个基于 Java 的、简单、省内存的 Excel 处理工具,它通过 SAX 模式(流式读取)来解析 Excel,大大降低了内存消耗,非常适合大数据量的导出和导入。

Java导出Excel数据,如何实现?-图1
(图片来源网络,侵删)

添加 Maven 依赖

在你的 pom.xml 文件中添加 EasyExcel 的依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.3.2</version> <!-- 请使用最新版本 -->
</dependency>

准备数据模型

创建一个与 Excel 表头对应的 Java 实体类,使用 @ExcelProperty 注解来指定表头的名称。

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
// 使用 Lombok 简化代码,也可以手动写 getter/setter
@Data
public class UserData {
    // value = "姓名" 对应 Excel 中的表头
    @ExcelProperty("姓名")
    private String name;
    @ExcelProperty("年龄")
    private Integer age;
    @ExcelProperty("邮箱")
    private String email;
}

编写导出服务

创建一个服务类,调用 EasyExcel 的 write 方法来写入数据。

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteTable;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class ExcelExportService {
    public void export(HttpServletResponse response) throws IOException {
        // 1. 设置响应头
        String fileName = "用户数据_" + System.currentTimeMillis() + ".xlsx";
        // URLEncoder.encode 用于处理文件名中的中文和空格
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
        // 2. 准备模拟数据
        List<UserData> dataList = new ArrayList<>();
        for (int i = 1; i <= 10000; i++) {
            UserData data = new UserData();
            data.setName("用户" + i);
            data.setAge(20 + (i % 30));
            data.setEmail("user" + i + "@example.com");
            dataList.add(data);
        }
        // 3. 使用 try-with-resources 确保 ExcelWriter 自动关闭
        try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), UserData.class).build()) {
            // 创建一个 sheet,名称为 "用户信息"
            WriteSheet writeSheet = EasyExcel.writerSheet("用户信息").build();
            // 将数据写入 sheet
            excelWriter.write(dataList, writeSheet);
        }
    }
}

代码解析:

Java导出Excel数据,如何实现?-图2
(图片来源网络,侵删)
  • HttpServletResponse: 在 Web 应用中,通常通过这个对象将文件流写入到 HTTP 响应中,供浏览器下载。
  • 响应头设置: 这是实现文件下载的关键。
    • Content-Type: 告诉浏览器这是一个 Excel 文件。
    • Content-disposition: attachment 表示附件,触发下载;filename 指定下载时的文件名。
  • EasyExcel.write(...): 开始构建一个写入操作。
    • response.getOutputStream(): 获取输出流。
    • UserData.class: 指定写入的数据模型。
  • excelWriter.write(...): 执行写入操作,将数据列表写入到指定的 Sheet 中。

使用 Apache POI

Apache POI 是 Java 操作 Office 文件最老牌、最强大的库,它功能全面,但 API 相对繁琐,且大数据量下内存消耗大。

添加 Maven 依赖

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.2.5</version> <!-- 请使用最新版本 -->
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.2.5</version> <!-- 用于支持 .xlsx 格式 -->
</dependency>

准备数据模型

POI 不需要特殊的注解,我们直接使用普通的 Java 类。

public class UserData {
    private String name;
    private Integer age;
    private String email;
    // 必须要有无参构造器
    public UserData() {}
    public UserData(String name, Integer age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }
    // Getters and Setters
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

编写导出服务

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public class PoiExcelExportService {
    public void export(HttpServletResponse response) throws IOException {
        // 1. 设置响应头 (与 EasyExcel 相同)
        String fileName = "用户数据_POI_" + System.currentTimeMillis() + ".xlsx";
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
        // 2. 创建 Excel 工作簿
        try (Workbook workbook = new XSSFWorkbook()) {
            // 3. 创建工作表
            Sheet sheet = workbook.createSheet("用户信息");
            // 4. 创建表头行
            Row headerRow = sheet.createRow(0);
            headerRow.createCell(0).setCellValue("姓名");
            headerRow.createCell(1).setCellValue("年龄");
            headerRow.createCell(2).setCellValue("邮箱");
            // 5. 准备模拟数据
            List<UserData> dataList = new ArrayList<>();
            for (int i = 1; i <= 10000; i++) {
                dataList.add(new UserData("用户" + i, 20 + (i % 30), "user" + i + "@example.com"));
            }
            // 6. 写入数据
            int rowNum = 1;
            for (UserData data : dataList) {
                Row row = sheet.createRow(rowNum++);
                row.createCell(0).setCellValue(data.getName());
                row.createCell(1).setCellValue(data.getAge());
                row.createCell(2).setCellValue(data.getEmail());
            }
            // 7. 自动调整列宽(可选)
            for (int i = 0; i < 3; i++) {
                sheet.autoSizeColumn(i);
            }
            // 8. 将工作簿写入输出流
            workbook.write(response.getOutputStream());
        }
    }
}

代码解析:

  • Workbook: 代表整个 Excel 文档。XSSFWorkbook 用于 .xlsx 格式,HSSFWorkbook 用于旧的 .xls 格式。
  • Sheet: 代表 Excel 中的一个工作表。
  • Row: 代表一行。
  • Cell: 代表一个单元格。
  • POI 的 API 更偏向于“手动操作”,需要你手动创建行、创建单元格并设置值,代码量比 EasyExcel 多。

高级功能:复杂样式和下拉列表

以 EasyExcel 为例,展示如何添加复杂样式和下拉列表。

创建样式工具类

import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import org.apache.poi.ss.usermodel.*;
public class ExcelStyleUtil {
    public static WriteHeaderCellStyle getHeadStyle() {
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        // 背景色
        headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        // 字体
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontHeightInPoints((short) 11);
        headWriteFont.setBold(true);
        headWriteCellStyle.setWriteFont(headWriteFont);
        // 居中
        headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        return headWriteCellStyle;
    }
    public static WriteCellStyle getContentStyle() {
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        // 字体
        WriteFont contentWriteFont = new WriteFont();
        contentWriteFont.setFontHeightInPoints((short) 10);
        contentWriteCellStyle.setWriteFont(contentWriteFont);
        // 垂直居中
        contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        // 水平居中
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        return contentWriteCellStyle;
    }
}

在导出服务中使用样式和下拉列表

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.handler.AbstractCellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import java.util.ArrayList;
import java.util.List;
public class AdvancedExcelExportService {
    public void exportWithStylesAndDropdown(HttpServletResponse response) throws Exception {
        // ... (响应头设置与前面相同) ...
        String fileName = "高级Excel_" + System.currentTimeMillis() + ".xlsx";
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
        // 1. 准备数据
        List<AdvancedUserData> dataList = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            dataList.add(new AdvancedUserData("产品" + i, "是", "正常"));
        }
        // 2. 创建下拉列表数据
        String[] dropdownList1 = {"是", "否"};
        String[] dropdownList2 = {"正常", "异常", "待处理"};
        try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), AdvancedUserData.class)
                .registerWriteHandler(new AbstractCellWriteHandler() {
                    @Override
                    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
                        // 在表头创建后执行
                        if (isHead) {
                            Sheet sheet = writeSheetHolder.getSheet();
                            // 为 "是否有效" 列 (第2列) 设置下拉列表
                            addDropdownValidation(sheet, 1, 1, dropdownList1);
                            // 为 "状态" 列 (第3列) 设置下拉列表
                            addDropdownValidation(sheet, 2, 2, dropdownList2);
                        }
                    }
                })
                .registerWriteHandler(ExcelStyleUtil.getHeadStyle()) // 注册表头样式
                .registerWriteHandler(new CustomCellWriteHandler()) // 注册内容样式
                .build()) {
            WriteSheet writeSheet = EasyExcel.writerSheet("高级数据").build();
            excelWriter.write(dataList, writeSheet);
        }
    }
    /**
     * 添加下拉列表验证
     * @param sheet 工作表
     * @param firstCol 起始列
     * @param lastCol 结束列
     * @param list 下拉列表内容
     */
    private void addDropdownValidation(Sheet sheet, int firstCol, int lastCol, String[] list) {
        DataValidationHelper helper = sheet.getDataValidationHelper();
        DataValidationConstraint constraint = helper.createExplicitListConstraint(list);
        CellRangeAddressList addressList = new CellRangeAddressList(1, 65535, firstCol, lastCol); // 从第二行到最后一行
        DataValidation validation = helper.createValidation(constraint, addressList);
        // 防止输入下拉列表以外的值
        validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
        validation.createErrorPrompt("错误", "请从下拉列表中选择有效的值!");
        sheet.addValidationData(validation);
    }
}
// 自定义内容样式处理器
class CustomCellWriteHandler extends AbstractCellWriteHandler {
    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        if (!isHead) {
            WriteCellStyle contentStyle = ExcelStyleUtil.getContentStyle();
            Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
            CellStyle cellStyle = contentStyle.buildCellStyle(workbook);
            cell.setCellStyle(cellStyle);
        }
    }
}
// 高级数据模型
class AdvancedUserData {
    @ExcelProperty("产品名称")
    private String productName;
    @ExcelProperty("是否有效")
    private String isValid;
    @ExcelProperty("状态")
    private String status;
    // 构造器、getter/setter...
}
场景 推荐方案 理由
常规数据导出 EasyExcel 代码简洁,性能好,内存占用低
大数据量导出 (万行+) EasyExcel 基于 SAX 模式,不会 OOM
需要复杂样式、图表、公式 Apache POI 功能最全面,API 底层但强大
旧项目维护 (JXL) JXL 如果不想引入新依赖,可以继续使用,但新项目请勿选用

对于绝大多数现代 Java 项目,EasyExcel 是毫无疑问的最佳选择,它既简单易用,又能满足高性能的需求,只有在遇到 EasyExcel 无法解决的极端复杂场景时,才需要考虑使用 Apache POI。

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