一个优雅、通用、零侵入的 CSV 导出工具类(Java 实战)

在业务系统中,CSV 导出几乎是一个绕不开的需求。

看起来很简单,但真正做过的人一定踩过这些坑:

  • Excel 打开 CSV,中文乱码
  • 字段里有逗号、引号,整行错位
  • 每个导出都写一套 DTO,代码重复又丑
  • 工具类和业务对象强绑定,复用性极差

最近在项目中,我整理了一个通用 CSV 导出工具类,使用下来非常顺手,索性分享出来,或许也能帮到你。


一、设计目标

在写这个工具类之前,我给自己定了几个目标:

  1. 不依赖任何具体业务对象
  2. 支持任意 DTO / VO / 聚合对象
  3. Excel 直接打开,不出现中文乱码
  4. CSV 格式严格、安全,不怕特殊字符
  5. 使用方式要足够简单

最终的实现,也基本满足了这些要求。


二、核心设计思路

1️⃣ 使用泛型 + Function 映射行数据

核心方法签名如下:

java 复制代码
public static <T> String exportCsv(
        List<String> headers,
        List<T> dataList,
        Function<T, List<Object>> rowMapper)

这里的关键点是 Function<T, List<Object>>:

  • 工具类 完全不知道 T 是什么
  • 每一行如何映射,由调用方决定
  • 不侵入 DTO,也不需要额外注解

这种设计方式在公共工具类中非常友好。


2️⃣ 主动写入 UTF-8 BOM,解决 Excel 中文乱码

java 复制代码
sb.append("\uFEFF");

这是一个非常容易被忽略、但极其重要的细节。

在 Windows 环境下,Excel 对 UTF-8 的识别并不稳定,不写 BOM,中文大概率乱码。

写入 BOM 几乎没有成本,却能解决 90% 的问题,非常值得。


3️⃣ CSV 字段统一加双引号,彻底避免格式问题

java 复制代码
return "\"" + val.replace("\"", "\"\"") + "\"";

CSV 的坑主要来自:

  • 字段中包含逗号 ,
  • 包含双引号 "
  • 包含换行符

这里采用的是 RFC 4180 标准做法:

  • 所有字段统一加双引号
  • 字段内的 " 转义为 ""

简单、粗暴、但极其稳定。


三、完整工具类代码

java 复制代码
package com.fang.industry.service.common.utils;

import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * CSV工具类
 */
public class CsvUtils {

    /**
     * 通用CSV导出方法
     *
     * @param headers   表头列表(中文)
     * @param dataList  数据源列表
     * @param rowMapper 行数据映射函数
     */
    public static <T> String exportCsv(
            List<String> headers,
            List<T> dataList,
            Function<T, List<Object>> rowMapper) {

        StringBuilder sb = new StringBuilder();

        // 1. 写入 BOM,防止 Excel 打开中文乱码
        sb.append("\uFEFF");

        // 2. 写入表头
        sb.append(String.join(",", headers)).append("\n");

        // 3. 写入数据行
        if (dataList != null) {
            for (T data : dataList) {
                List<Object> fields = rowMapper.apply(data);
                String rowStr = fields.stream()
                        .map(CsvUtils::formatCsvField)
                        .collect(Collectors.joining(","));
                sb.append(rowStr).append("\n");
            }
        }
        return sb.toString();
    }

    /**
     * 格式化 CSV 字段
     */
    private static String formatCsvField(Object field) {
        if (field == null) {
            return "";
        }
        String val = field.toString();
        return "\"" + val.replace("\"", "\"\"") + "\"";
    }
}

四、使用示例

示例 1:导出普通 DTO 列表

数据对象
java 复制代码
@Data
@AllArgsConstructor
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String remark;
}

导出 CSV
java 复制代码
List<String> headers = List.of("用户ID", "姓名", "年龄", "备注");

List<User> users = List.of(
        new User(1L, "张三", 18, "正常用户"),
        new User(2L, "李四", 25, "包含,逗号"),
        new User(3L, "王五", 30, "包含\"引号\"")
);

String csv = CsvUtils.exportCsv(
        headers,
        users,
        user -> List.of(
                user.getId(),
                user.getName(),
                user.getAge(),
                user.getRemark()
        )
);

生成的 CSV 用 Excel 打开完全正常。


示例 2:Spring Boot 接口直接下载 CSV

java 复制代码
@GetMapping("/export")
public void exportCsv(HttpServletResponse response) throws IOException {

    List<String> headers = List.of("ID", "姓名", "年龄");
    List<User> users = userService.list();

    String csv = CsvUtils.exportCsv(
            headers,
            users,
            u -> List.of(u.getId(), u.getName(), u.getAge())
    );

    response.setContentType("text/csv;charset=UTF-8");
    response.setHeader("Content-Disposition",
            "attachment; filename=\"users.csv\"");

    response.getWriter().write(csv);
}

五、适用场景 & 扩展思考

这个工具类特别适合:

  • 后台管理系统 CSV 导出
  • 临时分析数据导出
  • 中小数据量(非百万级)场景

如果数据量特别大,可以进一步优化为:

  • 使用 Writer 流式写出
  • 或直接对接文件下载流

六、总结

这个 CSV 工具类的核心特点是:

  • 通用:不绑定任何业务对象
  • 安全:彻底规避 CSV 格式坑
  • 实用:Excel 打开即用,不乱码

如果你也在项目中经常写 CSV 导出,不妨试试这种写法,或许能让你的工具类库更干净一些。


如果你觉得有用,欢迎点赞、收藏。

后面如果有机会,也会继续分享一些真正来自业务一线的实战代码。

相关推荐
NE_STOP4 分钟前
Spring AI Alibaba - MCP入门
java
nice_lcj5207 分钟前
【从零理解 Spring AI · 07】对话记忆:让模型记住上一句说了什么
java·spring ai·ai应用开发·chatmemory·对话记忆
总要学点什么1 小时前
第98篇:三维特效-材质线效果——给一条线换十种材质,虚线的图案只填了一半
开发语言·前端·javascript·ecmascript·gis·webgl·材质
田甲1 小时前
做一款 Type-C 母座读卡器:GL3224 双卡方案实测
c语言·开发语言
SL_staff1 小时前
制造业交付崩盘?用跨部门节点协同计划打通销售、生产与质量链路
java·开源·产品
严肃小白兔1 小时前
MATLAB 绘图与科研论文插图制作:图形设计、统计表达与矢量导出
开发语言·matlab·科研绘图
文丑颜不良啊2 小时前
LeetCode2261~2280题目解析
java·leetcode
SL_staff2 小时前
告别规则黑盒:JVS-Rules 在金融风控中的可视化决策编排实践
java·产品·全栈
逃逸线LOF3 小时前
服务网关Gateway
java·运维·gateway
Funlang3 小时前
给机器写的 JavaScript 造个笼子
开发语言·javascript·ecmascript