CSV文件导出

一、txt、csv、tsv文件

txt、csv、tsv都属于文本文件

26de8390633c4fb4972ce60a931a908d.png


csv又有叫做Char-separated values(字符分隔值类型),通过字符值进行分隔。

但因为半角逗号在数据中出现的的可能性比较大,所以经常会使用文本包装符来标识逗号为数据中的一部分,或者直接使用其它特殊符号作为分隔符。

二、csv文件规范

  1. 每一行记录位于一个单独的行上,用回车换行符CRLF(\r\n)分割。
  2. 文件中的最后一行记录可以有结尾回车换行符,也可以没有。
  3. 第一行可以存在一个可选的标题头,格式和普通记录行的格式一样。
  4. 标题头要包含文件记录字段对应的名称,应该有和记录字段一样的数量。
  5. 在标题头行和普通行每行记录中,会存在一个或多个由半角逗号(,)分隔的字段。整个文件中每行应包含相同数量的字段,空格也是字段的一部分,不应被忽略。每一行记录最后一个字段后不能跟逗号。(通常用逗号分隔,也有其他字符分隔的CSV,需事先约定)
  6. 每个字段可用也可不用半角双引号(")(文本包装符)括起来(如Microsoft的Excel就根本不用双引号)。如果字段没有用引号括起来,那么该字段内部不能出现双引号字符。
  7. 字段中若包含回车换行符、双引号或者逗号,该字段需要用双引号括起来。
  8. 如果用双引号括字段,那么出现在字段内的双引号前必须再加一个双引号进行转义。

三、csv使用场景


csv文件经常用于导出大批量数据(csv比excel更轻量级,更适合大批量数据)。

csv与excel对比:

  • csv只能用于存储纯文本内容,excel不仅支持纯文本内容还支持二进制数据
  • csv可以看做是excel的轻量级简单版实现,excel比csv更加强大
  • csv文件可以被excel软件直接打开,csv文件一般用于表格数据的传输

四、Java中的csv类库


java中的csv的类库主要有以下几类:

  • javacsv:javacsv在2014-12-10就不维护了
  • opencsv:opencsv是apache的项目,至今仍在维护

1. javacsv
2. opencsv

opencsv是一个用Java来分析和生成csv文件的框架。通常用来bean的写入csv文件和从csv文件读出bean,并支持注解的方式。

maven依赖:

<dependency>
    <groupId>com.opencsv</groupId>
    <artifactId>opencsv</artifactId>
    <version>5.6</version>
</dependency>


写入器
ab21270acfec4ab6ae08a5af43af9f6e.png
读取器
3ba83aae81d44fa3ae638853aa308d0a.png  
解析器
b1de1f334ab34d3bbd531345283cc6f9.png
注解
3c8e3db0ab5f43d6914c44dc4e333818.png

48b98cd8ea784811af425180130c1e0a.png
映射策略
MappingStrategy接口

public interface MappingStrategy<T> {
    void captureHeader(CSVReader var1) throws IOException, CsvRequiredFieldEmptyException;

    String[] generateHeader(T var1) throws CsvRequiredFieldEmptyException;

    /** @deprecated */
    @Deprecated
    default boolean isAnnotationDriven() {
        return false;
    }

    T populateNewBean(String[] var1) throws CsvBeanIntrospectionException, CsvFieldAssignmentException, CsvChainedException;

    default void setErrorLocale(Locale errorLocale) {
    }

    void setType(Class<? extends T> var1) throws CsvBadConverterException;

    default void setProfile(String profile) {
        throw new UnsupportedOperationException();
    }

    default void ignoreFields(MultiValuedMap<Class<?>, Field> fields) throws IllegalArgumentException {
        throw new UnsupportedOperationException();
    }

    String[] transmuteBean(T var1) throws CsvFieldAssignmentException, CsvChainedException;
}


075ba3366ab148068e95f52e295be174.png


MappingStrategy的实现类

93c9f90e026f457190f2020b1bf3c5af.png
335010267c5c4ba090bfe4af240e6996.png
① ColumnPositionMappingStrategy

使用该映射策略需要csv文件没有标题行。该策略通过设置列的下标位置来指定列的顺序,有两种方式来设置列的下标:

通过CsvBindByPosition、CsvCustomBindByPosition、CsvBindAndJoinByPosition、CsvBindAndSplitByPosition注解来设置列的下标
通过setColumnMapping(String… columnMapping)方法来设置列的下标
② HeaderColumnNameMappingStrategy

该映射策略用于有标题行的csv文件。该策略通过指定比较器来指定列的顺序:

通过setColumnOrderOnWrite(Comparator writeOrder)指定比较器
关于标题列的名称:

默认使用bean的字段名称大写作为标题列的名称
如果使用CsvBindByName、CsvCustomBindByName、CsvBindAndJoinByName、CsvBindAndSplitByName注解的column属性指定列名称,则使用该值,否则使用bean的字段名称大写作为标题列的名称
③ HeaderColumnNameTranslateMappingStrategy

该映射策略用于有标题行的csv文件。该策略通过映射Map来指定标题列名与bean的属性名映射关系。

映射Map的key=标题列名,value=bean的属性名。

需要注意:

  • 该映射策略只适用于读取csv文件时,指定标题列名与bean的属性名的映射关系
  • 该映射策略不适用于写入csv文件时,指定bean的属性名与标题列名的映射关系(不要误解)

过滤器
391169d091904a7797b42eeb2dcf6045.png
主要方法:boolean allowLine(String[] line)

入参中的line表示一行数据的集合
返回值为false的这行数据被将被过滤掉
构建器
9a8dd91be12c4d1d91e25b661ef941d7.png
写入方式
User类:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {


    public String userId;

    public String userName;

    public String sex;
}


User1类:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User1 {

    @CsvBindByPosition(position = 0)
    public String userId;

    @CsvBindByPosition(position = 1)
    public String userName;

    @CsvBindByPosition(position = 2)
    public String sex;
}


User2类:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User2 {

    @CsvBindByName(column = "用户ID")
    public String userId;

    @CsvBindByName(column = "用户名")
    public String userName;

    @CsvBindByName(column = "性别")
    public String sex;
}


① 简单的写入
CSVWriter的主要参数:

  • Writer writer:指定需要写入的源文件
  • char separator:分隔符(默认逗号)
  • char quotechar:文本边界符(默认双引号)
  • 如果数据中包含分隔符,需要使用文本边界符包裹数据。通常用双引号、单引号或斜杠作为文本边界符
  • char escapechar:转义字符(默认双引号)
  • String lineend:行分隔符(默认为\n)

使用方法: 

  /**
     * 简单的写入
     * @throws Exception
     */
    private static void csvWriter() throws Exception {
        // 写入位置
        String classpath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        String fileName = classpath+"test/demo.csv";
        // 标题行
        String[] titleRow = {"用户ID", "用户名", "性别"};
        // 数据行
        ArrayList<String[]> dataRows = new ArrayList<>();
        String[] dataRow1 = {"1", "张三", "男"};
        String[] dataRow2 = {"2", "李四", "男"};
        String[] dataRow3 = {"3", "翠花", "女"};
        dataRows.add(dataRow1);
        dataRows.add(dataRow2);
        dataRows.add(dataRow3);

        OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(fileName), Charset.forName("UTF-8"));
        // 1. 通过new CSVWriter对象的方式直接创建CSVWriter对象
        // CSVWriter csvWriter = new CSVWriter(writer);
        // 2. 通过CSVWriterBuilder构造器构建CSVWriter对象
        CSVWriter csvWriter = (CSVWriter) new CSVWriterBuilder(writer)
                .build();
        // 写入标题行
        csvWriter.writeNext(titleRow, false);
        // 写入数据行
        csvWriter.writeAll(dataRows, false);
        csvWriter.close();
    }


demo.csv内容:

用户ID,用户名,性别

1,张三,男
2,李四,男
3,翠花,女


② 基于位置映射的写入
使用方法:   

 /**
     * 基于位置映射的写入
     * @throws Exception
     */
    private static void beanToCsvByPosition() throws Exception {
        String classpath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        String fileName = classpath+"test/demo.csv";

        List<User> list = new ArrayList<>();
        list.add(new User("1", "张三", "男"));
        list.add(new User("2", "李四", "男"));
        list.add(new User("3", "翠花", "女"));

        OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(fileName), Charset.forName("UTF-8"));
        ColumnPositionMappingStrategy<User> strategy = new ColumnPositionMappingStrategy();
        // 未指定的列不写入
        String[] columns = new String[] { "userId", "userName", "sex"};
        strategy.setColumnMapping(columns);
        strategy.setType(User.class);
        // 如果需要标题行,可这样写入
        // CSVWriter csvWriter = (CSVWriter) new CSVWriterBuilder(writer)
        //         .build();
        // String[] titleRow = {"用户ID", "用户名", "性别"};
        // csvWriter.writeNext(titleRow, false);

        StatefulBeanToCsv<User> statefulBeanToCsv = new StatefulBeanToCsvBuilder<User>(writer)
                .withMappingStrategy(strategy)
                .withApplyQuotesToAll(false)
                .build();
        statefulBeanToCsv.write(list);
        writer.close();
    }


demo.csv内容:

1,张三,男
2,李四,男
3,翠花,女


③ 基于CsvBindByPosition注解映射的写入
使用方法:   

/**
     * 基于CsvBindByPosition注解映射的写入
     * @throws Exception
     */
    private static void beanToCsvByPositionAnnotation() throws Exception {
        String classpath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        String fileName = classpath+"test/demo.csv";

        List<User1> list = new ArrayList<>();
        list.add(new User1("1", "张三", "男"));
        list.add(new User1("2", "李四", "男"));
        list.add(new User1("3", "翠花", "女"));
        // 未使用@CsvBindByPosition注解的列不写入
        OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(fileName), Charset.forName("UTF-8"));

        // 如果需要标题行,可这样写入
        // CSVWriter csvWriter = (CSVWriter) new CSVWriterBuilder(writer)
        //         .build();
        // String[] titleRow = {"用户ID", "用户名", "性别"};
        // csvWriter.writeNext(titleRow, false);

        StatefulBeanToCsv<User1> statefulBeanToCsv = new StatefulBeanToCsvBuilder<User1>(writer)
                .withApplyQuotesToAll(false)
                .build();
        statefulBeanToCsv.write(list);
        writer.close();
    }


demo.csv内容:

1,张三,男
2,李四,男
3,翠花,女


④ 基于列名映射的写入
使用方法:   

 /**
     * 基于列名映射的写入
     * @throws Exception
     */
    private static void beanToCsvByName() throws Exception {
        String classpath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        String fileName = classpath+"test/demo.csv";

        List<User> list = new ArrayList<>();
        list.add(new User("1", "张三", "男"));
        list.add(new User("2", "李四", "男"));
        list.add(new User("3", "翠花", "女"));

        OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(fileName), Charset.forName("UTF-8"));
        // 可通过比较器指定列的顺序
        // 标题行的列名默认为bean的字段名大写
        HeaderColumnNameMappingStrategy<User> strategy = new HeaderColumnNameMappingStrategy<>();
        HashMap<String, Integer> columnOrderMap = new HashMap<>();
        columnOrderMap.put("USERID", 1);
        columnOrderMap.put("SEX", 10);
        columnOrderMap.put("USERNAME", 100);
        strategy.setColumnOrderOnWrite(Comparator.comparingInt(column -> (columnOrderMap.getOrDefault(column, 0))));
        strategy.setType(User.class);

        StatefulBeanToCsv<User> statefulBeanToCsv = new StatefulBeanToCsvBuilder<User>(writer)
                .withMappingStrategy(strategy)
                .withApplyQuotesToAll(false)
                .build();
        statefulBeanToCsv.write(list);
        writer.close();
    }


demo.csv内容:

USERID,SEX,USERNAME
1,男,张三
2,男,李四
3,女,翠花


⑤ 基于CsvBindByName注解映射的写入
使用方法: 

  /**
     * 基于CsvBindByName注解映射的写入
     * @throws Exception
     */
    private static void beanToCsvByNameAnnotation() throws Exception {
        String classpath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        String fileName = classpath+"test/demo.csv";

        List<User2> list = new ArrayList<>();
        list.add(new User2("1", "张三", "男"));
        list.add(new User2("2", "李四", "男"));
        list.add(new User2("3", "翠花", "女"));

        OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(fileName), Charset.forName("UTF-8"));
        // 可通过比较器指定列的顺序
        // 通过CsvBindByName注解的column属性,指定标题行的列名
        HeaderColumnNameMappingStrategy<User2> strategy = new HeaderColumnNameMappingStrategy<>();
        // 注意这里的key是指的标题行的列名
        HashMap<String, Integer> columnOrderMap = new HashMap<>();
        columnOrderMap.put("用户ID", 1);
        columnOrderMap.put("用户名", 10);
        columnOrderMap.put("性别", 100);
        strategy.setColumnOrderOnWrite(Comparator.comparingInt(column -> (columnOrderMap.getOrDefault(column, 0))));
        strategy.setType(User2.class);

        StatefulBeanToCsv<User2> statefulBeanToCsv = new StatefulBeanToCsvBuilder<User2>(writer)
                .withMappingStrategy(strategy)
                .withApplyQuotesToAll(false)
                .build();
        statefulBeanToCsv.write(list);
        writer.close();
    }


demo.csv内容:

用户ID,用户名,性别
1,张三,男
2,李四,男
3,翠花,女


读取方式
通过简单的写入写入的数据

① 简单的读取
使用方法:

  /**
     * 简单的读取
     * @throws Exception
     */
    private static void csvReader() throws Exception {
        String classpath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        String fileName = classpath+"test/demo.csv";
        InputStreamReader reader = new InputStreamReader(new FileInputStream(fileName), Charset.forName("UTF-8"));
        CSVReader csvReader = new CSVReaderBuilder(reader).build();
        List<String[]> list = csvReader.readAll();
        for (String[] strings : list) {
            System.out.println(JSON.toJSONString(strings));
        }
        csvReader.close();
    }

 

控制台日志:

["用户ID","用户名","性别"]
["1","张三","男"]
["2","李四","男"]
["3","翠花","女"]


② 基于位置映射的读取
通过基于位置映射的写入写入的数据

使用方法:

   

 /**
     * 基于位置映射的读取
     * @throws Exception
     */
    private static void csvToBeanByPosition() throws Exception {
        String classpath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        String fileName = classpath+"test/demo.csv";
        InputStreamReader reader = new InputStreamReader(new FileInputStream(fileName), Charset.forName("UTF-8"));
        // 不需要标题行,列的顺序通过列位置映射指定
        ColumnPositionMappingStrategy<User> strategy = new ColumnPositionMappingStrategy();
        String[] columns = new String[] { "userId", "userName", "sex"};
        strategy.setColumnMapping(columns);
        strategy.setType(User.class);
        CsvToBean<User> csvToBean = new CsvToBeanBuilder<User>(reader)
                .withMappingStrategy(strategy)
                .build();
        List<User> list = csvToBean.parse();
        for (User user : list) {
            System.out.println(JSON.toJSONString(user));
        }
        reader.close();
    }


控制台日志:

{"sex":"男","userId":"1","userName":"张三"}
{"sex":"男","userId":"2","userName":"李四"}
{"sex":"女","userId":"3","userName":"翠花"}



③ 基于CsvBindByPosition注解映射的读取
通过基于CsvBindByPosition注解映射的写入写入的数据

使用方法:

 

  /**
     * 基于CsvBindByPosition注解映射的读取
     * @throws Exception
     */
    private static void csvToBeanByPositionAnnotation() throws Exception {
        String classpath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        String fileName = classpath+"test/demo.csv";
        InputStreamReader reader = new InputStreamReader(new FileInputStream(fileName), Charset.forName("UTF-8"));
        // 不需要标题行,列的顺序通过CsvBindByPosition注解的position属性指定
        CsvToBean<User1> csvToBean = new CsvToBeanBuilder<User1>(reader)
                .withType(User1.class)
                .build();
        List<User1> list = csvToBean.parse();
        for (User1 user : list) {
            System.out.println(JSON.toJSONString(user));
        }
        reader.close();
    }


控制台日志:

{"sex":"男","userId":"1","userName":"张三"}
{"sex":"男","userId":"2","userName":"李四"}
{"sex":"女","userId":"3","userName":"翠花"}


④ 基于列名映射的读取
通过基于列名映射的写入写入的数据

使用方法:

 

  /**
     * 基于列名映射的读取
     * @throws Exception
     */
    private static void csvToBeanByName() throws Exception {
        String classpath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        String fileName = classpath+"test/demo.csv";
        InputStreamReader reader = new InputStreamReader(new FileInputStream(fileName), Charset.forName("UTF-8"));
        // bean的字段名称大写为标题列名
        CsvToBean<User> csvToBean = new CsvToBeanBuilder<User>(reader)
                .withType(User.class)
                .build();
        List<User> list = csvToBean.parse();
        for (User user : list) {
            System.out.println(JSON.toJSONString(user));
        }
        reader.close();
    }


控制台日志:

{"sex":"男","userId":"1","userName":"张三"}
{"sex":"男","userId":"2","userName":"李四"}
{"sex":"女","userId":"3","userName":"翠花"}


⑤ 基于CsvBindByName注解映射的读取
通过基于CsvBindByName注解映射的写入写入的数据

使用方法:

   

private static void csvToBeanByNameAnnotation() throws Exception {
        String classpath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        String fileName = classpath+"test/demo.csv";
        InputStreamReader reader = new InputStreamReader(new FileInputStream(fileName), Charset.forName("UTF-8"));
        // CsvBindByName注解的column属性为标题列名
        CsvToBean<User2> csvToBean = new CsvToBeanBuilder<User2>(reader)
                .withType(User2.class)
                .build();
        List<User2> list = csvToBean.parse();
        for (User2 user : list) {
            System.out.println(JSON.toJSONString(user));
        }
        reader.close();
    }


控制台日志:

{"sex":"男","userId":"1","userName":"张三"}
{"sex":"男","userId":"2","userName":"李四"}
{"sex":"女","userId":"3","userName":"翠花"}


⑥ 基于列名转换映射的读取
通过基于CsvBindByName注解映射的读取写入的数据

使用方法:

public class MyCsvToBeanFilter implements CsvToBeanFilter {

    @Override
    public boolean allowLine(String[] line) {
        // 过滤掉用户名为李四的行
        if("李四".equals(line[1])){
            return false;
        }
        return true;
    }
}

    /**
     * 基于列名转换映射的读取
     * @throws Exception
     */
    private static void csvToBeanByColumnNameTranslateMapping() throws Exception {
        String classpath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        String fileName = classpath+"test/demo.csv";
        InputStreamReader reader = new InputStreamReader(new FileInputStream(fileName), Charset.forName("UTF-8"));
        // 指定标题列名和bean列名映射关系
        HeaderColumnNameTranslateMappingStrategy<User> strategy = new HeaderColumnNameTranslateMappingStrategy<>();
        // key:标题列名,value:bean的属性名
        HashMap<String, String> columnMappingMap = new HashMap<>();
        columnMappingMap.put("用户ID", "userId");
        columnMappingMap.put("性别", "sex");
        columnMappingMap.put("用户名", "userName");
        strategy.setColumnMapping(columnMappingMap);
        strategy.setType(User.class);
        CsvToBean<User> csvToBean = new CsvToBeanBuilder<User>(reader)
                .withMappingStrategy(strategy)
                .withFilter(new MyCsvToBeanFilter())
                .withIgnoreField(User2.class, User2.class.getField("userId"))// 忽略userId属性
                .build();
        List<User> list = csvToBean.parse();
        for (User user : list) {
            System.out.println(JSON.toJSONString(user));
        }
        reader.close();
    }


控制台日志:

{"sex":"男","userName":"张三"}
{"sex":"女","userName":"翠花"}


自定义列表和列顺序
3. commons-csv
4. hutool CsvUtil(扩展)

原文链接:https://blog.csdn.net/JokerLJG/article/details/127450857

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值