SpringBoot导入和导出Csv文件(二十八)

见死亡,见众生,见自在

上一章简单介绍了 SpringBoot上传和下载文件(二十七),如果没有看过,请观看上一章

一. CSV 文件

以下内容,来自百度百科

一.一 CSV文件简介

逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),

其文件以纯文本形式存储表格数据(数字和文本)。纯文本意味着该文件是一个字符序列,不含必须像二进制数字那样被解读的数据。CSV文件由任意数目的记录组成,记录间以某种换行符分隔;每条记录由字段组成,字段间的分隔符是其它字符或字符串,

最常见的是逗号或制表符。通常,所有记录都有完全相同的字段序列。通常都是纯文本文件

建议使用WORDPAD或是记事本来开启,再则先另存新档后用EXCEL开启,也是方法之一

一.二 规则

1 开头是不留空,以行为单位。

2 可含或不含列名,含列名则居文件第一行。

3 一行数据不跨行,无空行。

4 以半角逗号(即,)作分隔符,列为空也要表达其存在。

5列内容如存在半角引号(即"),替换成半角双引号("")转义,即用半角引号(即"")将该字段值包含起来。

6文件读写时引号,逗号操作规则互逆。

7内码格式不限,可为 ASCII、Unicode 或者其他。

8不支持数字

9不支持特殊字符

一.三 Csv文件举例

image-20211109175257681

文件形式 形如:

id,name,sex,age,description
1,两个蝴蝶飞_1,,21,我是第1个,我的名字是:两个蝴蝶飞_1
2,两个蝴蝶飞_2,,22,我是第2个,我的名字是:两个蝴蝶飞_2
3,两个蝴蝶飞_3,,23,我是第3个,我的名字是:两个蝴蝶飞_3
4,两个蝴蝶飞_4,,24,我是第4个,我的名字是:两个蝴蝶飞_4
5,两个蝴蝶飞_5,,25,我是第5个,我的名字是:两个蝴蝶飞_5
6,两个蝴蝶飞_6,,26,我是第6个,我的名字是:两个蝴蝶飞_6
7,两个蝴蝶飞_7,,27,我是第7个,我的名字是:两个蝴蝶飞_7
8,两个蝴蝶飞_8,,28,我是第8个,我的名字是:两个蝴蝶飞_8
9,两个蝴蝶飞_9,,29,我是第9个,我的名字是:两个蝴蝶飞_9
10,两个蝴蝶飞_10,,30,我是第10个,我的名字是:两个蝴蝶飞_10

二. hutool 关于Csv处理的工具

官网地址是: CSV文件处理工具-CsvUtil

在 pom.xml 中添加依赖

  <!--添加hutool-all, 处理csv文件解析-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.10</version>
        </dependency>

在 cn.hutool.core.text.csv 包下

image-20211109175816853

接下来,老蝴蝶简单讲解一下.

对应的官方文档地址是: https://apidoc.gitee.com/loolly/hutool/

二.一 读取

二.一 .一 CsvReadConfig

	/** 是否首行做为标题行,默认false */
	protected boolean containsHeader;
	/** 是否跳过空白行,默认true */
	protected boolean skipEmptyRows = true;
	/** 每行字段个数不同时是否抛出异常,默认false */
	protected boolean errorOnDifferentFieldCount;
	/** 定义开始的行(包括),此处为原始文件行号 */
	protected long beginLineNo;
	/** 结束的行(包括),此处为原始文件行号 */
	protected long endLineNo = Long.MAX_VALUE-1;

在读取文件时,可以进行相应的配置.

注意,是否将首行默认为标题行 containsHeader 为 false

二.一.二 CsvReader

image-20211109190613041

二.二 写入

二.二.一 CsvWriteConfig

写入文件时的配置

	/**
	 * 是否始终使用文本分隔符,文本包装符,默认false,按需添加
	 */
	protected boolean alwaysDelimitText;
	/**
	 * 换行符
	 */
	protected char[] lineDelimiter = {CharUtil.CR, CharUtil.LF};   //对应的是  \r \n

二.二.二 CsvWriter

image-20211109191150291

二.三 公共部分

二.三.一 总的封装数据 CsvData

public class CsvData implements Iterable<CsvRow>, Serializable {
	private static final long serialVersionUID = 1L;
    //标题行
	private final List<String> header;
    //行数据
	private final List<CsvRow> rows;
}

二.三.二 每一行数据对象 CsvRow

public final class CsvRow implements List<String> {

	/** 原始行号 */
	private final long originalLineNumber;
    //这一行的  标题和对应的索引位置  如 name 0  表示 name字段是在第0位,最开始的位置
	final Map<String, Integer> headerMap;
    // 这一行的  内容
	final List<String> fields;
}

image-20211109191808952

二.三.三 工具类 CsvUtil

image-20211109191931049

基本就这些常用的信息,可以满足日常开发的相关需求.

写几个常见的例子,就明白怎么使用了.

三. Csv的操作使用

与上一章节的 Excel 操作例子相同

提取了一个工具类,用于获取数据

public class DataUtil {
    /**
     * 获取用户的数据
     * @date 2021/11/8 13:51
     * @author zk_yjl
     * @param name
     * @return java.util.List<top.yueshushu.learn.pojo.User>
     */
    public static List<User> createDataList(String name) {
        List<User> result=new ArrayList<>();
        for(int i=1;i<=10;i++){
            User user=new User();
            user.setId(i);
            user.setName(name+"_"+i);
            user.setSex(i%2==0?"女":"男");
            user.setAge(20+i);
            user.setDescription("我是第"+i+"个,我的名字是:"+user.getName());
            result.add(user);
        }
        return result;
    }

}

不要忘记在 pom.xml 中添加依赖

  <!--添加hutool-all, 处理csv文件解析-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.10</version>
        </dependency>

三.一 Csv写入文件

放置在 WriteTest 测试类里面

三.一.一 writeBeans 方式写入数据

/**
     * 简单的 csv文件写入
     * @date 2021/11/8 13:51
     * @author zk_yjl
     * @param
     * @return void
     */
    @Test
    public void simpleWriteTest() throws Exception{
        //1. 大批量的业务处理,最后获取组装相应的数据信息。
        List<User> userList= DataUtil.createDataList("两个蝴蝶飞");
        //2. 存储的文件地址
        String filePath="D:\\csv\\simple.csv";
        //获取 CsvWriter
        CsvWriter csvWriter = CsvUtil.getWriter(filePath, Charset.forName("UTF-8"));
        // 写入注释
        csvWriter.writeComment("一个简单的csv文件");
        //写入新的一行
        csvWriter.writeLine();
        //写入内容
        csvWriter.writeBeans(userList);
        //关闭内容
        csvWriter.close();
        log.info("写入文件成功");
    }

运行后,查看数据

image-20211109192723285

标题行 是对应的 bean的属性值.

三.一.二 注解在实体上,自定义标题头输出

可以在实体类上,通过 @Alias 注解来自定义标题头

@Data
public class User {
    @Alias("编号")
    private Integer id;
    @Alias("姓名")
    private String name;
 //   @Alias("性别")
    private String sex;
  //  @Alias("年龄")
    private Integer age;
    @Alias("描述信息")
    private String description;
}

进行测试

  /**
     * 写入标题头文件
     * @date 2021/11/8 14:06
     * @author zk_yjl
     * @param
     * @return void
     */
    @Test
    public void headerTest() throws Exception{
        //1. 大批量的业务处理,最后获取组装相应的数据信息。
        List<User> userList= DataUtil.createDataList("两个蝴蝶飞");
        //2. 存储的文件路径
        String filePath="D:\\csv\\header.csv";
        //3. 获取CsvWriter
        CsvWriter csvWriter = CsvUtil.getWriter(filePath, Charset.forName("UTF-8"));
        //4. 写入注入
        csvWriter.writeComment("带着自定义标题头的文件");
        //写入内容
        csvWriter.writeBeans(userList);
        //关闭
        csvWriter.close();
        log.info(">>写入标题头文件成功");
    }

image-20211109193040328

编号,姓名,描述信息, 通过 @Alias 注解 进行了自定义标题.

一般开发中,导出 .csv 文件,都是采用 导出 Java Bean 的方式.

复杂的,就需要通过 CsvData 实体进行构建了.

三.一.三 CsvData 构造实现导出数据

 /**
     * 写入CsvData数据
     * @date 2021/11/8 14:06
     * @author zk_yjl
     * @param
     * @return void
     */
    @Test
    public void csvDataTest() throws Exception{
        //1. 大批量的业务处理,最后获取组装相应的数据信息。
        List<User> userList= DataUtil.createDataList("两个蝴蝶飞");
        //2. 存储的文件路径
        String filePath="D:\\csv\\data.csv";
        //3. 获取CsvWriter
        CsvWriter csvWriter = CsvUtil.getWriter(filePath, Charset.forName("UTF-8"));
        //4. 写入注入
        csvWriter.writeComment("写入CsvData文件");
        //写入标题头
        List<String> header=Arrays.asList("编号","姓名","性别","年龄","描述");

        List<CsvRow> csvRowList=new ArrayList<>();

        int i=0;
        //写入行
        for(User user:userList){
            //构建 headerMap
            Map<String, Integer> headerMap=new HashMap<>();
            headerMap.put("编号",0);
            headerMap.put("姓名",1);
            headerMap.put("性别",2);
            headerMap.put("年龄",3);
            headerMap.put("描述",4);

            //放置该行的值
            List<String> fieldList=new ArrayList<>();
            fieldList.add(user.getId()+"");
            fieldList.add(user.getName()+"");
            fieldList.add(user.getSex()+"");
            fieldList.add(user.getAge()+"");
            fieldList.add(user.getDescription()+"");
            //构建 CsvRow 对象
            CsvRow csvRow=new CsvRow(i,headerMap,fieldList);
            csvRowList.add(csvRow);
            i++;
        }
        CsvData csvData=new CsvData(header,csvRowList);
        csvWriter.write(csvData);
        //关闭
        csvWriter.close();
        log.info(">>写入CsvData 文件成功");
    }

运行程序

image-20211109193401681

三.二 Csv读取文件

放置在 ReadTest 测试类里面,读取的文件用刚才生成的那些文件.

三.二.一 简单读取数据

 /**
    * 简单文件的读取
    * @date 2021/11/8 14:15
    * @author zk_yjl
    * @param
    * @return void
    */
    @Test
    public void simpleTest(){
         //1. 读取的文件路径
        String filePath="D:\\csv\\simple.csv";
        //构建 CsvReader 对象
        CsvReader csvReader = CsvUtil.getReader();
        //进行读取
        CsvData csvData = csvReader.read(new File(filePath), Charset.forName("UTF-8"));
        //进行处理
        //获取相应的内容
        int rowCount = csvData.getRowCount();
        log.info(">>>读取了{}行",rowCount);
        //获取行数据
        List<CsvRow> rows = csvData.getRows();
        for(CsvRow csvRow:rows){
            //打印出该行的内容信息
            List<String> rawList = csvRow.getRawList();
            log.info(">>获取内容信息:"+rawList);
        }
    }

image-20211109193722315

读取了 11行, 将 id,name,sex 这些应该是标题行的信息,变成了正常的内容形式.

可以通过 CsvReadConfig 进行相关的配置.

三.二.二 CsvReadConfig 读取配置简单使用

   /**
     * 读取信息,读取成 CsvData 形式.
     * @date 2021/11/8 14:39
     * @author zk_yjl
     * @param
     * @return void
     */
    @Test
    public void config1Test(){
        //1. 读取的文件路径
        String filePath="D:\\csv\\simple.csv";
        //2. 进行配置
        CsvReadConfig csvReadConfig=new CsvReadConfig();
        csvReadConfig.setSkipEmptyRows(true);
        //包括标题行 
        csvReadConfig.setContainsHeader(true);
        //进行了读取,设置
        //构建 CsvReader 对象
        CsvReader csvReader = CsvUtil.getReader(csvReadConfig);
        //进行读取
        CsvData csvData = csvReader.read(new File(filePath), Charset.forName("UTF-8"));
        //进行处理
        List<String> header = csvData.getHeader();
        log.info(">>>读取的文件标题头为:"+header.toString());
        //获取相应的内容
        int rowCount = csvData.getRowCount();
        log.info(">>>读取了{}行",rowCount);
        //获取行数据
        List<CsvRow> rows = csvData.getRows();
        for(CsvRow csvRow:rows){
            List<String> rawList = csvRow.getRawList();
            log.info(">>获取内容信息:"+rawList);
        }
    }

image-20211109194001487

读取时,会默认把一行变成标题头,

剩余的是内容, 读取是正常的.

三.二.三 CsvReadConfig 读取配置较复杂使用

可以进行动态的配置,从哪一行开始读取,读取到第几行.

  /**
     * 配置读取的信息
     * @date 2021/11/8 14:39
     * @author zk_yjl
     * @param
     * @return void
     */
    @Test
    public void config2Test(){
        //1. 读取的文件路径
        String filePath="D:\\csv\\simple.csv";
        //2. 进行配置
        CsvReadConfig csvReadConfig=new CsvReadConfig();
        csvReadConfig.setSkipEmptyRows(true);
        csvReadConfig.setContainsHeader(true);
        //设置最开始读取的行号, 注意,这一行,会被当成标题行的.
        csvReadConfig.setBeginLineNo(0);
        //读取的行号
        csvReadConfig.setEndLineNo(6);
        //进行了读取,设置
        //构建 CsvReader 对象
        CsvReader csvReader = CsvUtil.getReader(csvReadConfig);
        //进行读取
        CsvData csvData = csvReader.read(new File(filePath), Charset.forName("UTF-8"));
        //进行处理
        List<String> header = csvData.getHeader();
        log.info(">>>读取的文件标题头为:"+header.toString());
        //获取相应的内容
        int rowCount = csvData.getRowCount();
        log.info(">>>读取了{}行",rowCount);
        //获取行数据
        List<CsvRow> rows = csvData.getRows();
        for(CsvRow csvRow:rows){
           /* List<String> rawList = csvRow.getRawList();
            log.info(">>获取内容信息:"+rawList);*/
           log.info(">>>>");
            Map<String, String> fieldMap = csvRow.getFieldMap();
            for(Map.Entry<String,String> fieldEntry:fieldMap.entrySet()){
                log.info(">>>>>>>>:"+fieldEntry.getKey()+":"+fieldEntry.getValue());
            }
        }
    }

image-20211109194732718

如果修改

        csvReadConfig.setBeginLineNo(4);  // 由0 变成4

再次运行

image-20211109194901459

三.二.四 读取数据,封装成 Java Bean

  /**
   * 读取信息,放置到bean 里面
   * @date 2021/11/8 14:39
   * @author zk_yjl
   * @param
   * @return void
   */
    @Test
    public void headerTest() throws Exception{
        //1. 读取的文件路径
        String filePath="D:\\csv\\header.csv";
        //进行了读取,设置
        //2. 进行配置
        CsvReadConfig csvReadConfig=new CsvReadConfig();
        csvReadConfig.setSkipEmptyRows(true);
        csvReadConfig.setContainsHeader(true);
        //构建 CsvReader 对象
        CsvReader csvReader = CsvUtil.getReader(csvReadConfig);
        //直接读取,封装成 Bean
        List<User> userList = csvReader.read(new FileReader(new File(filePath)), User.class);
        for(User user:userList){
            log.info(">>"+user);
        }
    }

可以在 实体类上 通过 @Alias 注解 将文件中的标题与类中的属性进行关联起来.

image-20211109195229430

这就是一些关于 Csv的基本操作.

四. SpringBoot 实现 Csv文件的导入和导出

按照上一章节的模式,进行修改.

四.一 前端页面

<!doctype html>
<!--注意:引入thymeleaf的名称空间-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type"content="text/html;charset=UTF-8">
    <title>文件上传</title>
    <link rel="StyleSheet" href="webjars/bootstrap/3.4.1/css/bootstrap.css" type="text/css">
</head>
<body class="container">
<p class="h1">导入文件</p>
<form  action="uploadCsv" method="post" enctype="multipart/form-data">
    <div class="form-group">
        <div class="custom-file">
            <input type="file" class="custom-file-input" id="file" name="file">
            <label class="file-label" for="file">选择文件</label>
        </div>
    </div>
    <button type="submit" class="btn btn-primary">上传</button>
</form>

<p class="h1">文件导出</p>

<a href="downloadCsv">导出Csv文件</a>

<script type="text/javascript" src="webjars/jquery/3.5.1/jquery.js"></script>
<script type="text/javascript" src="webjars/bootstrap/3.4.1/js/bootstrap.js"></script>
</body>
</html>

image-20211109195538432

四.二 Csv文件导入

四.二.一 导入逻辑处理

 /**
     * 上传Excel文件,获取文件里面的内容.
     * @date 2021/11/4 21:12
     * @author zk_yjl
     * @return
     */
    @PostMapping("/uploadCsv")
    @ResponseBody
    public String uploadCsv(@RequestParam MultipartFile file) throws IOException {
        String realPath =uploadFilePath;
        File newFile = new File(realPath);
        // 如果文件夹不存在、则新建
        if (!newFile.exists()){
            newFile.mkdirs();
        }
        // 上传
        File uploadFile=new File(newFile, file.getOriginalFilename());
        file.transferTo(uploadFile);
        //读取文件信息,主要是这一行
        List<User> userList = CsvUtil.getReader().read(new FileReader(uploadFile), User.class);
        userList.forEach(n->{
            log.info("输出内容:>>>"+n);
            //可以进行具体的业务操作
        });
        String uploadPath=realPath+"/"+file.getOriginalFilename();
        return "上传文件成功,地址为:"+uploadPath;
    }

四.二.二 导入上传测试

将 header.csv 文件进行上传

image-20211109195802405

后端控制台 将 文件中的内容打印出来

image-20211109195837225

四.三 Csv文件导出

四.三.一 导出文件逻辑处理

 /**
     * 下载Excel文件
     * @date 2021/11/4 21:12
     * @author zk_yjl
     * @return
     */
    @GetMapping("/downloadCsv")
    @ResponseBody
    public void downloadCsv(HttpServletResponse response) throws IOException {
        //1. 通过传入的参数,和相关的业务代码逻辑处理,获取相应的数据.
        //2. 将数据进行转换,转换成 List<User> 的形式.
        List<User> dataList= DataUtil.createDataList("两个蝴蝶飞");
        //将数据进行下载.
        // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
        try {
            //响应类型
            response.setContentType("multipart/form-data");
            response.setCharacterEncoding("utf-8");
            //进行下载
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
            String fileName = URLEncoder.encode("员工表", "UTF-8").replaceAll("\\+", "%20");
            //响应的是  .csv 文件的后缀
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".csv");
            // 这里需要设置不关闭流
            String filePath=uploadFilePath+File.separator+fileName+".csv";
            File file=new File(filePath);
            if(!file.exists()){
                file.createNewFile();
            }
            //将数据,写入到 文件里面。 主要是这一行代码逻辑
            CsvUtil.getWriter(file, Charset.forName("UTF-8")).writeBeans(dataList).close();
            downloadFile(response,file);
            //将该文件删除
            file.delete();
        } catch (Exception e) {
            // 重置response
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            Map<String, String> map = new HashMap<String, String>();
            map.put("status", "failure");
            map.put("message", "下载文件失败" + e.getMessage());
            JSONObject jsonObject=new JSONObject();;
            response.getWriter().println(jsonObject.toString());
        }
    }

    /**
     * @return boolean
     * @Description 下载文件
     * @Param response,file
     **/
    public boolean downloadFile(HttpServletResponse response, File file) {
        FileInputStream fileInputStream = null;
        BufferedInputStream bufferedInputStream = null;
        OutputStream os = null;
        try {
            fileInputStream = new FileInputStream(file);
            bufferedInputStream = new BufferedInputStream(fileInputStream);
            os = response.getOutputStream();
            //MS产本头部需要插入BOM
            //如果不写入这几个字节,会导致用Excel打开时,中文显示乱码
            os.write(new byte[]{(byte) 0xEF, (byte) 0xBB, (byte) 0xBF});
            byte[] buffer = new byte[1024];
            int i = bufferedInputStream.read(buffer);
            while (i != -1) {
                os.write(buffer, 0, i);
                i = bufferedInputStream.read(buffer);
            }
            return true;
        } catch (IOException e) {
            e.printStackTrace();

        } finally {
            //关闭流
            if (os != null) {
                try {
                    os.flush();
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bufferedInputStream != null) {
                try {
                    bufferedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            file.delete();
        }
        return false;
    }

四.三.二 导出测试

点击导出文件,可以进行相应的下载.

image-20211109200100697

本章节的代码放置在 github 上:

https://github.com/yuejianli/springboot/tree/develop/SpringBoot_Csv

谢谢您的观看,如果喜欢,请关注我,再次感谢 !!!

要在Spring Boot应用程序中导出数据到CSV文件,需要执行以下步骤: 1.添加CSV依赖项 在pom.xml文件中添加以下依赖项: ``` <dependency> <groupId>com.opencsv</groupId> <artifactId>opencsv</artifactId> <version>5.2</version> </dependency> ``` 2.创建CSVWriter 使用CSVWriter类将数据写入CSV文件。在Spring Boot应用程序中,可以使用以下代码创建一个CSVWriter: ``` CSVWriter writer = new CSVWriter(new FileWriter("data.csv")); ``` 3.将数据写入CSV文件 使用CSVWriter的writeNext方法将数据写入CSV文件。例如,以下代码将一行数据写入CSV文件: ``` String[] data = {"John", "Doe", "john.doe@example.com"}; writer.writeNext(data); ``` 4.关闭CSVWriter 最后,关闭CSVWriter以确保所有数据都已写入CSV文件: ``` writer.close(); ``` 完整示例: ``` @GetMapping("/export") public void exportToCSV(HttpServletResponse response) throws IOException { response.setContentType("text/csv"); response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"data.csv\""); CSVWriter writer = new CSVWriter(new FileWriter("data.csv")); String[] header = {"First Name", "Last Name", "Email"}; writer.writeNext(header); List<User> users = userService.getAllUsers(); for (User user : users) { String[] data = {user.getFirstName(), user.getLastName(), user.getEmail()}; writer.writeNext(data); } writer.close(); } ``` 此代码块将从UserService中获取所有用户,将其写入CSV文件并将其下载到用户的计算机上。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

两个蝴蝶飞

你的鼓励,是老蝴蝶更努力写作的

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值