Java实现CSV文件的导出

绪论

相信大家对于后台导出数据到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:

点击导出用户,再点击csv文件,excel打开:
我们看到,号码出现科学计数,我们点击任何一个单元格三次就可以了
用文本工具打开:
具体的就不多说了,大家看源码带就可以啦。 最后,祝大家平安夜快乐哈!!

我创建了一个技术讨论QQ群,主要面向Java开发,同时也会面对Android开发和前端兴趣爱好者,有兴趣的朋友可以加群和大家讨论技术相关问题。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值