Java實現CSV檔案的匯出

深夜程猿發表於2017-12-25

緒論

相信大家對於後臺匯出資料到excel表的需求很熟悉的。最近在開發專案過程中,就有使用者的匯入匯出功能。開始我思路是使用者匯出匯入都使用excel格式,但是到後面發現 其實在匯出大量資料的時候,excel表是有很大局性的。一次匯出10W條資料的時候,發現匯出為excel失敗,檢視錯誤資訊就是excel表對於資料的行數有限制,excel2003 是65535條,excel2007會更多(還是會有限制)。考慮到管理員電腦的excel版本有高有低(必須相容最低版本03),加上匯出這麼多資料,記憶體佔用會比較大,弄不好會出現記憶體洩漏。隨著使用者量的不斷增加,匯出為excel顯得越來不可取。於是就採用匯出為csv 格式,加上csv可以使用excel表開啟,這看來是不錯的做法。

什麼是CSV

那什麼是csv?格式又是怎麼樣的呢? 大家看一下百度百科的定義: CSV:逗號分隔值(Comma-Separated Values,CSV,有時也稱為字元分隔值,因為分隔字元也可以不是逗號), 其檔案以純文字形式儲存表格資料(數字和文字)。純文字意味著該檔案是一個字元序列,不含必須像二進位制數 字那樣被解讀的資料。CSV檔案由任意數目的記錄組成,記錄間以某種換行符分隔;每條記錄由欄位組成,欄位 間的分隔符是其它字元或字串,最常見的是逗號或製表符。通常,所有記錄都有完全相同的欄位序列。通常都 是純文字檔案。 給大家舉例子:
使用者暱稱,使用者賬號,使用者等級
聖誕老人1,13800138000,VIP7
聖誕老人2,13800138000,VIP7
聖誕老人3,13800138000,VIP8
第一行寫資料對應的標題: 使用者暱稱,使用者賬號,使用者等級
第二行根據標題的順序寫資料,一行表示一條資料,多條資料多行寫就可以了:聖誕老人2,13800138000,VIP7

程式碼實現

原始碼
這裡結合程式碼給大家講解一下一個具體demo的實現,使用的是spring-boot來搭建web環境的,不熟悉spring-boot的朋友,可以使用springmvc也行,其實是一樣的。 不需要依賴任何jar。

首先是Controller層的實現,就不多解釋了,註釋有了大家可以看懂

package com.example.demo.controller;

import com.example.demo.dto.UserExportToCsvDTO;
import com.example.demo.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * @author wunanliang
 * @date 2017/12/24
 * @since 1.0.0
 */
@RestController
public class FileController {


    @Autowired
    private FileService fileService;

    @PostMapping("/api/v1/export/csv/users")
    public void exportCsv(HttpServletResponse response, HttpServletRequest request) throws IOException {

        // 模擬匯出資料,這裡資料可以是從資料庫獲取回來的,也可以是前端傳過來再解析的
        // 這裡的資料應該放在dao層獲取的,就先簡單放在這裡,大家不必介意,只是demo演示
        List<UserExportToCsvDTO> users = new ArrayList<>();
        users.add(new UserExportToCsvDTO("13800138001", "聖誕老人1", "VIP1"));
        users.add(new UserExportToCsvDTO("13800138002", "聖誕老人2", "VIP7"));
        users.add(new UserExportToCsvDTO("13800138003", "聖誕老人3", "VIP8"));
        // csv檔名字,為了方便預設給個名字,當然名字可以自定義,看實際需求了
        String fileName = "我是csv檔案.csv";
        // 解決不同瀏覽器出現的亂碼
        fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString());
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM.toString());
        response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"; filename*=utf-8''" + fileName);
        FileCopyUtils.copy(fileService.exportUsersToCsv(users), response.getOutputStream());
    }
}

複製程式碼

我們再來看FileService程式碼:

package com.example.demo.service;

import com.example.demo.CsvUtils;
import com.example.demo.dto.UserExportToCsvDTO;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;

/**
 * 為了方便,就不寫介面和實現分離了
 *
 * @author wunanliang
 * @date 2017/12/24
 * @since 1.0.0
 */
@Service
public class FileService {


    /**
     * 匯出使用者到csv檔案
     *
     * @param users 匯出的資料(使用者)
     * @return
     */
    public byte[] exportUsersToCsv(List<UserExportToCsvDTO> users) {
        // 為了方便,也不寫dao層
        List<LinkedHashMap<String, Object>> exportData = new ArrayList<>(users.size());
        // 行資料
        for (UserExportToCsvDTO user : users) {
            LinkedHashMap<String, Object> rowData = new LinkedHashMap<>();
            rowData.put("1", user.getUsername());
            rowData.put("2", user.getNickname());
            rowData.put("3", user.getLevel());
            exportData.add(rowData);
        }
        LinkedHashMap<String, String> header = new LinkedHashMap<>();
        header.put("1", "使用者賬號");
        header.put("2", "使用者暱稱");
        header.put("3", "使用者等級");
        return CsvUtils.exportCSV(header, exportData);
    }

}

複製程式碼

CsvUtils程式碼:

package com.example.demo;

import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * CSV檔案幫助類
 *
 * @author wunanliang
 * @date 2017/12/24
 * @since 1.0.0
 */
public class CsvUtils {

    /**
     * 匯出csv檔案
     *
     * @param headers    內容標題
     *                   注意:headers型別是LinkedHashMap,保證遍歷輸出順序和新增順序一致。
     *                   而HashMap的話不保證新增資料的順序和遍歷出來的資料順序一致,這樣就出現
     *                   資料的標題不搭的情況的
     * @param exportData 要匯出的資料集合
     * @return
     */
    public static byte[] exportCSV(LinkedHashMap<String, String> headers, List<LinkedHashMap<String, Object>> exportData) {

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        BufferedWriter buffCvsWriter = null;

        try {
            // 編碼gb2312,處理excel開啟csv的時候會出現的標題中文亂碼
            buffCvsWriter = new BufferedWriter(new OutputStreamWriter(baos, "gb2312"));
            // 寫入cvs檔案的頭部
            Map.Entry propertyEntry = null;
            for (Iterator<Map.Entry<String, String>> propertyIterator = headers.entrySet().iterator(); propertyIterator.hasNext(); ) {
                propertyEntry = propertyIterator.next();
                buffCvsWriter.write("\"" + propertyEntry.getValue().toString() + "\"");
                if (propertyIterator.hasNext()) {
                    buffCvsWriter.write(",");
                }
            }
            buffCvsWriter.newLine();
            // 寫入檔案內容
            LinkedHashMap row = null;
            for (Iterator<LinkedHashMap<String, Object>> iterator = exportData.iterator(); iterator.hasNext(); ) {
                row = iterator.next();
                for (Iterator<Map.Entry> propertyIterator = row.entrySet().iterator(); propertyIterator.hasNext(); ) {
                    propertyEntry = propertyIterator.next();
                    buffCvsWriter.write("\"" + propertyEntry.getValue().toString() + "\"");
                    if (propertyIterator.hasNext()) {
                        buffCvsWriter.write(",");
                    }
                }
                if (iterator.hasNext()) {
                    buffCvsWriter.newLine();
                }
            }
            // 記得重新整理緩衝區,不然數可能會不全的,當然close的話也會flush的,不加也沒問題
            buffCvsWriter.flush();
        } catch (IOException e) {

        } finally {
            // 釋放資源
            if (buffCvsWriter != null) {
                try {
                    buffCvsWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return baos.toByteArray();
    }
}

複製程式碼

下面看看實現效果:
輸入url:

Java實現CSV檔案的匯出
點選匯出使用者,再點選csv檔案,excel開啟:
Java實現CSV檔案的匯出
我們看到,號碼出現科學計數,我們點選任何一個單元格三次就可以了
Java實現CSV檔案的匯出
用文字工具開啟:
Java實現CSV檔案的匯出
具體的就不多說了,大家看原始碼帶就可以啦。 最後,祝大家平安夜快樂哈!!

我建立了一個技術討論QQ群,主要面向Java開發,同時也會面對Android開發和前端興趣愛好者,有興趣的朋友可以加群和大家討論技術相關問題。

Java實現CSV檔案的匯出

相關文章