绪论
相信大家对于后台导出数据到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 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 users) { // 为了方便,也不写dao层 List> exportData = new ArrayList<>(users.size()); // 行数据 for (UserExportToCsvDTO user : users) { LinkedHashMap rowData = new LinkedHashMap<>(); rowData.put("1", user.getUsername()); rowData.put("2", user.getNickname()); rowData.put("3", user.getLevel()); exportData.add(rowData); } LinkedHashMap 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 headers, List> 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> 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> iterator = exportData.iterator(); iterator.hasNext(); ) { row = iterator.next(); for (Iterator 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(); } }
大家可以自行测试。
源码地址: https://github.com/WuNanliang/demo-csv
原文来源链接:https://juejin.im/post/5a3f8c7df265da4311206c59