相信你們對於後臺導出數據到excel表的需求很熟悉的。最近在開發項目過程當中,就有用戶的導入導出功能。開始我思路是用戶導出導入都使用excel格式,可是到後面發現 其實在導出大量數據的時候,excel表是有很大局性的。一次導出10W條數據的時候,發現導出爲excel失敗,查看錯誤信息就是excel表對於數據的行數有限制,excel2003 是65535條,excel2007會更多(仍是會有限制)。考慮到管理員電腦的excel版本有高有低(必須兼容最低版本03),加上導出這麼多數據,內存佔用會比較大,弄很差會出現內存泄漏。隨着用戶量的不斷增長,導出爲excel顯得越來不可取。因而就採用導出爲csv 格式,加上csv可使用excel表打開,這看來是不錯的作法。前端
那什麼是csv?格式又是怎麼樣的呢? 你們看一下百度百科的定義: CSV:逗號分隔值(Comma-Separated Values,CSV,有時也稱爲字符分隔值,由於分隔字符也能夠不是逗號), 其文件以純文本形式存儲表格數據(數字和文本)。純文本意味着該文件是一個字符序列,不含必須像二進制數 字那樣被解讀的數據。CSV文件由任意數目的記錄組成,記錄間以某種換行符分隔;每條記錄由字段組成,字段 間的分隔符是其它字符或字符串,最多見的是逗號或製表符。一般,全部記錄都有徹底相同的字段序列。一般都 是純文本文件。 給你們舉例子:
用戶暱稱,用戶帳號,用戶等級
聖誕老人1,13800138000,VIP7
聖誕老人2,13800138000,VIP7
聖誕老人3,13800138000,VIP8
第一行寫數據對應的標題: 用戶暱稱,用戶帳號,用戶等級
第二行根據標題的順序寫數據,一行表示一條數據,多條數據多行寫就能夠了:聖誕老人2,13800138000,VIP7
java
源碼
這裏結合代碼給你們講解一下一個具體demo的實現,使用的是spring-boot來搭建web環境的,不熟悉spring-boot的朋友,可使用springmvc也行,實際上是同樣的。 不須要依賴任何jar。git
首先是Controller層的實現,就很少解釋了,註釋有了你們能夠看懂github
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代碼:web
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代碼:spring
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: 數據庫
我建立了一個技術討論QQ羣,主要面向Java開發,同時也會面對Android開發和前端興趣愛好者,有興趣的朋友能夠加羣和你們討論技術相關問題。api