首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java 生成 CSV 檔案完整實戰:UTF-8 BOM、大數據流式寫入、Web 下載

     編輯:Java 程式設計入門

核心需求

  • Excel 開啟 CSV 不亂碼 → 必須寫入 UTF-8 BOM (EF BB BF)。
  • 大數據量 (百萬列) → 流式寫入,避免 OOM。
  • Web 介面下載 → HttpServletResponse 設定正確 Header。

工具類 CSVUtils

public class CSVUtils {
    // 生成 CSV 檔案 (流式寫入)
    public static File createCSVFile(List<Map<String, Object>> data,
                                     LinkedHashMap<String, String> headers, // key=欄位名, value=表頭
                                     String outDir, String fileName) throws IOException {
        File dir = new File(outDir);
        if (!dir.exists()) dir.mkdirs();

        File csvFile = File.createTempFile(fileName, ".csv", dir);
        try (BufferedWriter bw = new BufferedWriter(
                new OutputStreamWriter(new FileOutputStream(csvFile), StandardCharsets.UTF_8))) {

            // 寫入 UTF-8 BOM
            bw.write('\uFEFF');

            // 寫入表頭
            boolean first = true;
            for (String header : headers.values()) {
                if (!first) bw.write(',');
                bw.write(escapeCsv(header));
                first = false;
            }
            bw.newLine();

            // 寫入資料
            for (Map<String, Object> row : data) {
                first = true;
                for (String field : headers.keySet()) {
                    if (!first) bw.write(',');
                    Object val = row.get(field);
                    bw.write(escapeCsv(val == null ? "" : val.toString()));
                    first = false;
                }
                bw.newLine();
            }
        }
        return csvFile;
    }

    // CSV 轉義:包含逗號、換行、雙引號時用雙引號包裹,內部雙引號轉義為兩個雙引號
    private static String escapeCsv(String s) {
        if (s.contains(",") || s.contains("\n") || s.contains("\r") || s.contains("\"")) {
            return "\"" + s.replace("\"", "\"\"") + "\"";
        }
        return s;
    }

    // Web 下載
    public static void download(HttpServletResponse resp, File csvFile, String downloadName) throws IOException {
        resp.setContentType("text/csv;charset=UTF-8");
        resp.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(downloadName, "UTF-8") + "\"");
        resp.setHeader("Cache-Control", "no-cache, must-revalidate");

        try (InputStream in = new FileInputStream(csvFile);
             OutputStream out = resp.getOutputStream()) {
            byte[] buf = new byte[8192];
            int len;
            while ((len = in.read(buf)) > 0) out.write(buf, 0, len);
            out.flush();
        }
    }

    // 刪除暫存檔
    public static void deleteFile(File file) {
        if (file != null && file.exists()) file.delete();
    }
}

Controller 使用範例

@GetMapping("/export")
public void exportOrders(HttpServletResponse resp) throws IOException {
    // 1. 查詢資料 (分頁/游標避免 OOM)
    List<Map<String, Object>> data = orderService.exportAllOrders(); // 應使用 Stream/游標

    // 2. 定義表頭順序
    LinkedHashMap<String, String> headers = new LinkedHashMap<>();
    headers.put("orderNo", "訂單編號");
    headers.put("orderDate", "下單時間");
    headers.put("amount", "金額(元)");
    headers.put("status", "狀態");

    // 3. 生成並下載
    File csv = CSVUtils.createCSVFile(data, headers, "/tmp/export", "orders");
    try {
        CSVUtils.download(resp, csv, "orders_" + LocalDate.now() + ".csv");
    } finally {
        CSVUtils.deleteFile(csv);
    }
}

大數據優化建議

  1. 資料庫游標/分頁:MyBatis @Select + ResultHandler 或 Spring Data Stream<T> 逐行處理,不載入全量到記憶體。
  2. 非同步生成:超大檔案 (>100MB) 改為後台任務生成至物件存儲 (MinIO/S3),前端輪詢/下載連結。
  3. 壓縮下載:GZIPOutputStream 包裝 resp.getOutputStream(),Header 加 Content-Encoding: gzip。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved