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

Java Web 導出 TXT/CSV 檔案:Servlet 輸出流完整實作與中文檔名處理

     編輯:更多程式設計知識

在 Java Web 開發中,**導出文字檔(TXT/CSV)** 是常見需求。核心在於正確設定 HTTP 響應標頭、處理字元編碼、使用緩衝輸出流。本文提供兩個生產級範例:通用列表導出與 VIP 兌換碼導出,涵蓋中文檔名編碼、大數據流式寫入、資源安全釋放等關鍵細節。

核心技術要點

  1. Content-Type:text/plain; charset=UTF-8 或 text/csv
  2. Content-Disposition:attachment; filename="encodedName.txt" 觸發瀏覽器下載
  3. 檔名編碼:URLEncoder.encode(filename, "UTF-8") 解決中文檔名亂碼
  4. 流式寫入:BufferedOutputStream + ServletOutputStream,避免大數據佔用記憶體
  5. 資源管理:try-with-resources (Java 7+) 或 finally 區塊確保關閉流

範例一:Spring MVC 導出視頻列表 CSV

@RequestMapping(value = "/grab/export/csv", method = RequestMethod.GET)
public void exportCsv(HttpServletRequest request, HttpServletResponse response) {
    String userId = ServletRequestUtils.getStringParameter(request, "userId", "test");
    
    // 1. 取得業務資料
    List list = new ArrayList<>();
    try {
        list = sqlVideoList.selectSuccessDate(userId);
    } catch (SQLException e) {
        log.error("查詢失敗", e);
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        return;
    }

    // 2. 設定響應標頭
    response.setContentType("text/plain; charset=UTF-8");
    String fileName = "videolist";
    try {
        fileName = URLEncoder.encode(fileName, "UTF-8");
    } catch (UnsupportedEncodingException e) {
        log.warn("檔名編碼失敗", e);
    }
    response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + ".txt\"");

    // 3. 流式寫入
    try (ServletOutputStream outStr = response.getOutputStream();
         BufferedOutputStream buff = new BufferedOutputStream(outStr)) {

        StringBuilder write = new StringBuilder();
        String enter = "\r\n"; // Windows 換行,跨平台建議 System.lineSeparator()

        if (!list.isEmpty()) {
            for (VideoListModel model : list) {
                write.append(model.getUrl()).append(",");
                write.append(model.getTitle());
                write.append(enter);
            }
        }
        // 一次性寫入(資料量大時建議分批 flush)
        buff.write(write.toString().getBytes(StandardCharsets.UTF_8));
        buff.flush();
    } catch (IOException e) {
        log.error("檔案寫入失敗", e);
        // 此時響應可能已提交,無法再設定狀態碼
    }
}

範例二:Spring MVC 導出 VIP 兌換碼(含狀態轉換、分頁欄位)

@RequestMapping(value = "/{exchangeId}/{packageId}/export", method = RequestMethod.GET)
public void writeToTxt(@PathVariable String exchangeId,
                       @PathVariable String packageId,
                       HttpServletRequest request,
                       HttpServletResponse response) throws UnsupportedEncodingException {

    String schoolId = getSchoolId(request);
    
    // 1. 查詢資料
    VipCodeExample example = new VipCodeExample();
    example.createCriteria()
           .andSchoolIdEqualTo(schoolId)
           .andPackageIdEqualTo(packageId)
           .andExchangeIdEqualTo(exchangeId);
    List vipCodes = vipExchangeManager.getVipCode(example);

    // 2. 設定響應
    response.setContentType("text/plain; charset=UTF-8");
    String downloadName = vipCodes.isEmpty() ? "vip_codes" : vipCodes.get(0).getName();
    String encodedName = URLEncoder.encode(downloadName, "UTF-8");
    response.addHeader("Content-Disposition", "attachment; filename=\"" + encodedName + ".txt\"");

    // 3. 寫入處理
    try (ServletOutputStream outStr = response.getOutputStream();
         BufferedOutputStream buff = new BufferedOutputStream(outStr)) {

        StringBuilder write = new StringBuilder();
        String tab = "\t"; // CSV 建議用逗號,這裡用 Tab 分隔
        String enter = "\r\n";

        for (int i = 0; i < vipCodes.size(); i++) {
            VipCode code = vipCodes.get(i);
            write.append(i + 1).append(tab);           // 序號
            write.append(code.getExchangeCode()).append(tab); // 兌換碼
            write.append("normal".equals(code.getStatus()) ? "正常" : "已兌換");
            write.append(enter);
        }

        buff.write(write.toString().getBytes(StandardCharsets.UTF_8));
        buff.flush();
    } catch (IOException e) {
        log.error("導出失敗", e);
    }
}

進階優化:大數據量分批寫入(避免 OOM)

// 適用於百萬筆以上資料
try (ServletOutputStream out = response.getOutputStream();
     BufferedOutputStream buff = new BufferedOutputStream(out, 8192)) { // 8KB 緩衝

    String header = "序號\t兌換碼\t狀態\r\n";
    buff.write(header.getBytes(StandardCharsets.UTF_8));

    int batchSize = 1000;
    for (int i = 0; i < vipCodes.size(); i += batchSize) {
        int end = Math.min(i + batchSize, vipCodes.size());
        StringBuilder batch = new StringBuilder();
        for (int j = i; j < end; j++) {
            VipCode code = vipCodes.get(j);
            batch.append(j + 1).append('\t')
                 .append(code.getExchangeCode()).append('\t')
                 .append("normal".equals(code.getStatus()) ? "正常" : "已兌換")
                 .append('\n');
        }
        buff.write(batch.toString().getBytes(StandardCharsets.UTF_8));
        buff.flush(); // 定期刷新,釋放記憶體
    }
}

常見坑與解決方案

問題原因解決
中文檔名下載後亂碼瀏覽器對 RFC 5987 / RFC 6266 支援不一雙重編碼:filename*=UTF-8''{encoded} + filename="{fallback}"
導出內容中文亂碼未指定 charset 或編碼不一致統一使用 StandardCharsets.UTF_8,Content-Type 含 charset
大檔案導出記憶體溢位 (OOM)一次性載入所有資料到 StringBuilder分批查詢 + 分批寫入 + 適當 flush()
Excel 開啟 CSV 科學記號/前導零消失Excel 自動判斷格式匯入時指定文字格式,或改用 Apache POI 導出真正 .xlsx

總結

Java Web 導出文字檔的關鍵在於:**正確的 HTTP 標頭、UTF-8 全鏈路編碼、流式分批寫入、資源確定性釋放**。掌握這些核心點,即可應對從簡單報表到大數據導出的各種場景。建議新專案優先使用 try-with-resources 簡化資源管理,並引入 Apache Commons CSV 或 OpenCSV 處理複雜 CSV 格式(含逗號、換行、引號義)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved