opencsv实现Csv与Java对象转换(CSV)


使用opencsv实现Csv与Java对象转换(CSV)

参考文章:https://www.cnblogs.com/xiaogh/articles/17705590.html

OpenCSV 是一个用于 Java 的流行的库,用于读取和写入 CSV(逗号分隔值)文件。CSV 文件是一种常见的结构化数据存储和交换格式。

OpenCSV 提供了一种简单和方便的方式来在 Java 应用程序中处理 CSV 文件。

以下是 OpenCSV 的一些关键特点和用法示例:

  1. 读取 CSV 文件

  2. 写入 CSV 文件

  3. 自定义 CSV 解析和写入

    OpenCSV 允许您自定义 CSV 解析和写入的各个方面,例如指定分隔符(默认是逗号)、处理引号等。

  4. 使用注解处理 CSV

    OpenCSV 支持使用注解将 Java 对象映射到 CSV 记录。可以使用注解来定义如何将字段映射到 CSV 列。

    • **@CsvBindByName:**通过列名来绑定字段。

      你可以使用 column 属性指定 CSV 文件中的列名,OpenCSV 将根据列名将数据映射到相应的字段。

      @CsvBindByName(column = "Name")
      private String name;
      
    • @CsvBindByPosition:通过列的位置(索引)来绑定字段。

      你可以使用 position 属性指定 CSV 文件中的列索引,OpenCSV 将根据索引将数据映射到相应的字段。

      @CsvBindByPosition(position = 0)
      private int id;
      
    • @CsvBindByType:用于指定字段的数据类型。

      你可以使用 type 属性指定数据类型,OpenCSV 将尝试将 CSV 数据转换为指定的数据类型。

      @CsvBindByType(type = BigDecimal.class)
      private BigDecimal price;
      
    • @CsvDate:用于处理日期字段。

      你可以使用 value 属性指定日期的格式,OpenCSV 将尝试将 CSV 数据解析为指定格式的日期。

      @CsvDate("yyyy-MM-dd")
      private Date birthDate;
      
    • @CsvCustomBindByName:自定义绑定器。

      你可以使用 converter 属性指定一个自定义的绑定器类,该类负责将 CSV 数据转换为字段的值。

      @CsvCustomBindByName(column = "Price", converter = CustomPriceConverter.class)
      private BigDecimal price;
      
  5. 错误处理:

    OpenCSV 提供了处理 CSV 解析和写入过程中错误的机制,例如:处理格式不正确的 CSV 记录

  6. 处理文件头:

    可以通过单独读取文件头并使用文件头来映射数据到 Java 对象,轻松处理带有标题的 CSV 文件。

  7. CSV Bean 映射:

    OpenCSV 允许将 CSV 记录直接映射到 Java Bean,使用注解来定义映射关系,这样可以轻松处理结构化数据。

  8. 将 CSV 转换为 SQL:

    OpenCSV 还可以用于读取 CSV 数据并将其插入到 SQL 数据库中。


Csv与Java对象转换示例如下:
  1. 引入相关依赖

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

    注意:

    不同的版本之间可能有的方法没有,较新版本的功能比较多。

  2. 创建实体类

    @Data
    public class User {
    
        @CsvBindByName(column = "姓名")
        @CsvBindByPosition(position = 0)
        private String name;
    
        @CsvBindByName(column = "年龄")
        @CsvBindByPosition(position = 1)
        private Integer age;
    
        @CsvBindByName(column = "性别")
        @CsvBindByPosition(position = 2)
        private String sex;
    }
    
  3. 创建Csv工具类

    public class CsvUtils {
    
        /**
         * csv文件导入并转为java对象
         * @param fileName
         * @param clazz
         * @return
         * @param <T>
         */
        public static <T> List<T> readCSVFile(String fileName, Class<T> clazz){
            try {
                FileReader reader = new FileReader(fileName);
                CSVReader csvReader  = new CSVReader(reader);
    
                HeaderColumnNameMappingStrategy<T> strategy = new HeaderColumnNameMappingStrategy<>();
                strategy.setType(clazz);
    
                CsvToBean<T> csvToBean = new CsvToBeanBuilder<T>(clsvReader)
                        .withType(clazz)
                        //将第一行视为标题行
                        .withMappingStrategy(strategy)
                        .build();
    
                return csvToBean.parse();
    
            } catch (FileNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
    
        /**
         * 将java对象转为csv文件导出
         * @param fileName
         * @param list
         * @return
         * @param <T>
         */
        public static <T> void writeCSVFile(String fileName, List<T> list) {
    
            try {
                FileWriter writer = new FileWriter(fileName);
    
                StatefulBeanToCsv<T> beanToCsv = new StatefulBeanToCsvBuilder<T>(writer)
                        .withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
                        // 启用标题行
                        .withOrderedResults(true)
                        .build();
    
                // 添加标题行(将 Person 对象的字段名作为标题)
                writer.write("Name, Age, Sex\n");
    
                beanToCsv.write(list);
                writer.close();
    
            } catch (FileNotFoundException e) {
                throw new RuntimeException(e);
            } catch (CsvRequiredFieldEmptyException e) {
                throw new RuntimeException(e);
            } catch (CsvDataTypeMismatchException e) {
                throw new RuntimeException(e);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
    

    这里对CSVReader 、CsvToBean、StatefulBeanToCsv等进行详细分析:

    • CSVReader

      CSVReader 是 OpenCSV 库的核心类之一,用于读取 CSV 文件的内容并将其转换为 Java 对象或数据结构。

      它支持多种配置选项,可以适应不同的 CSV 文件格式和数据类型。

      常用方法:

      • readNext():读取 CSV 文件的下一行数据,并将其返回为一个字符串数组。如果已经达到文件末尾,则返回 null。
      • readAll():一次性读取整个 CSV 文件的所有行数据,并将其返回为一个 List,其中每个元素是一个字符串数组,表示一行数据。
      • skip():跳过指定数量的行。可以用于跳过 CSV 文件的标题行或不需要处理的行。
      • close():关闭 CSVReader,释放资源。在使用完 CSVReader 后,应该调用该方法以关闭文件流
      • getLinesRead():获取已经读取的行数。可以用于跟踪读取进度。
      • setSkipEmptyRows():设置是否跳过空行。默认情况下,CSVReader 不会跳过空行,但你可以使用这个方法来配置它。
      • setSafetySwitch():设置安全开关。当启用安全开关时,CSVReader 会检查文件是否以预期的行数结尾。如果不是,将抛出异常。
    • CsvToBean

      CsvToBean 类是 OpenCSV 库的一部分,用于将 CSV 数据解析为 Java 对象或数据结构。

      它支持多种配置选项,以适应不同的 CSV 文件格式和数据类型。通常,CsvToBean 被用于将 CSV 行映射到 Java Bean 类,使得 CSV 数据可以更方便地在 Java 应用程序中处理。

      常用方法:

      • parse():解析 CSV 数据并将其映射到 Java 对象。该方法返回一个包含 Java 对象的 List。

      • parse() 的重载方法:除了简单的 parse() 方法,CsvToBean 还提供了一些重载方法,用于更精细地控制解析过程。

        这些方法接受一个 CSVReader 对象作为参数,允许你指定要解析的 CSV 数据源。

      • withMappingStrategy():设置 CsvToBean 使用的映射策略,用于定义 CSV 行到 Java 对象的映射规则。

        通常,你需要创建一个自定义的映射策略类,并将其传递给 withMappingStrategy() 方法。

        如何定义映射策略类??见下文等等~~wow~ ⊙o⊙

      • withType():指定要映射到的目标 Java 类型。

        通常,你需要提供目标 Java Bean 类的类对象。

      • withSeparator():设置 CSV 文件中的分隔符。

        默认情况下,分隔符是逗号,但你可以使用该方法来指定其他分隔符。

      • withQuoteChar():设置用于引用包含特殊字符的字段的字符。

        默认情况下,引用字符是双引号,但你可以使用该方法来指定其他引用字符。

      • withThrowExceptions():设置是否在解析过程中抛出异常。

        默认情况下,异常被抑制,但你可以使用该方法来启用异常。

      • withOrderedResults():指定解析结果是否按照 CSV 文件中的顺序排序。

        默认情况下,结果是无序的,但你可以使用该方法来启用排序。

      • withFilter():设置一个自定义的过滤器,用于过滤解析的数据行。

    • StatefulBeanToCsv

      StatefulBeanToCsv 类是 OpenCSV 库的一部分,用于将 Java 对象或数据结构写入 CSV 文件——比如导出功能。

      它支持多种配置选项,以适应不同的 CSV 文件格式和数据类型。通常,StatefulBeanToCsv 被用于将 Java 对象的列表写入 CSV 文件,或者将 Java Bean 对象的属性写入 CSV 行中。

      常用方法:

      • write():将 Java 对象的列表写入 CSV 文件。

        这个方法接受一个包含 Java 对象的列表作为参数,将它们写入 CSV 文件。

      • write() 的重载方法:除了简单的 write() 方法,StatefulBeanToCsv 还提供了一些重载方法,用于更精细地控制写入过程。这些方法允许你指定要写入的 Java 对象列表、字段选择策略等。

      其余方法与CsvToBean类相似。

    • 常用的 MappingStrategy 实现(即映射策略)

      1. ColumnPositionMappingStrategy:这是 OpenCSV 默认的映射策略。

      它将 CSV 文件的列按照它们在文件中的位置与 Java Bean 的属性逐一对应。 例如,CSV 文件的第一列数据将映射到 Java Bean 的第一个属性,第二列数据映射到 Java Bean 的第二个属性,依此类推。

      1. HeaderColumnNameMappingStrategy:这个策略适用于包含列名称的 CSV 文件。 它将 CSV 文件的列名称与 Java Bean 的属性名称逐一对应。

      2. CustomMappingStrategy:如果你需要更灵活的映射策略,可以实现自定义的 MappingStrategy。

        你可以在自定义策略中定义如何将 CSV 数据映射到 Java Bean,以满足特定的需求。


  4. 来,上测试类进行测试吧!

    • CSV文件解析器工具类

      public class CsvUtils {
      
          /**
           * csv文件导入并转为java对象
           * @param fileName
           * @param clazz
           * @return
           * @param <T>
           */
          public static <T> List<T> readCSVFile(String fileName, Class<T> clazz){
              try {
                  FileReader reader = new FileReader(fileName);
                  CSVReader csvReader  = new CSVReader(reader);
      
                  HeaderColumnNameMappingStrategy<T> strategy = new HeaderColumnNameMappingStrategy<>();
                  strategy.setType(clazz);
      
                  CsvToBean<T> csvToBean = new CsvToBeanBuilder<T>(csvReader)
                          .withType(clazz)
                          //将第一行视为标题行
                          .withMappingStrategy(strategy)
                          .build();
      
                  return csvToBean.parse();
      
              } catch (FileNotFoundException e) {
                  throw new RuntimeException(e);
              }
          }
      
          /**
           * 将java对象转为csv文件导出
           * @param fileName
           * @param list
           * @return
           * @param <T>
           */
          public static <T> void writeCSVFile(String fileName, List<T> list) {
      
              try {
                  FileWriter writer = new FileWriter(fileName);
      
                  StatefulBeanToCsv<T> beanToCsv = new StatefulBeanToCsvBuilder<T>(writer)
                          .withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
                          // 启用标题行
                          .withOrderedResults(true)
                          .build();
      
                  // 添加标题行(将 Person 对象的字段名作为标题)
                  writer.write("Name, Age, Sex\n");
      
                  beanToCsv.write(list);
                  writer.close();
      
              } catch (FileNotFoundException e) {
                  throw new RuntimeException(e);
              } catch (CsvRequiredFieldEmptyException e) {
                  throw new RuntimeException(e);
              } catch (CsvDataTypeMismatchException e) {
                  throw new RuntimeException(e);
              } catch (IOException e) {
                  throw new RuntimeException(e);
              }
          }
      }
      
    • 创建映射的实体类,使用注解,定义属性相关的映射关系

      @Data
      public class User {
      
          @CsvBindByName(column = "姓名")
          @CsvBindByPosition(position = 0)
          private String name;
      
          @CsvBindByName(column = "年龄")
          @CsvBindByPosition(position = 1)
          private Integer age;
      
          @CsvBindByName(column = "性别")
          @CsvBindByPosition(position = 2)
          private String sex;
      }
      
    1. 将Java对象转换为csv文件

      @Test
      void ToCsv(){
      
          User user1 = new User();
          user1.setName("Tom");
          user1.setAge(22);
          user1.setSex("男");
      
          User user2 = new User();
          user2.setName("John");
          user2.setAge(21);
          user2.setSex("女");
      
          List<User> users = new ArrayList<>();
          users.add(user1);
          users.add(user2);
      
          //PATH = "C:\\Users\\16056\\Desktop\\test.csv"
          CsvUtils.writeCSVFile(PATH, users);
          System.out.println("执行完成");
      
      }
      
    2. 将csv文件转换为Java对象

      @Test
      void toObject()  {
      
          //PATH = "C:\\Users\\16056\\Desktop\\test.csv"
          List<User> users = CsvUtils.readCSVFile(PATH, User.class);
          for (User user : users){
              System.out.println(user);
          }
      }
      

OpenCSV 首次使用踩坑记录

  1. 导入的 csv 格式文件,文件中的数据在经过 opencsv 解析后,发现读取的第一列里的数据一直为空。 CSV文件:

    image-20241113150628273

    代码中的映射类:

    package com.example.springboot.entity;
    
    import com.opencsv.bean.CsvBindByName;
    import lombok.Data;
    
    import java.io.Serializable;
    
    /**
     * @Description:
     * @title: XxlJobUser
     * @Author mhd
     * @Date: 2024/11/12 17:25
     * @Version 1.0
     */
    @Data
    public class XxlJobUser implements Serializable {
    
        private static final long serialVersionUID = -8669034503989075986L;
    
        @CsvBindByName(column = "主键")
    //    @CsvBindByPosition(position = 0)
        private Integer id;
    
        @CsvBindByName(column = "姓名")
    //    @CsvBindByPosition(position = 1)
        private String username;
    
        @CsvBindByName(column = "密码")
    //    @CsvBindByPosition(position = 2)
        private String password;
    
        @CsvBindByName(column = "角色")
    //    @CsvBindByPosition(position = 3)
        private Integer role;
    
        @CsvBindByName(column = "权限")
    //    @CsvBindByPosition(position = 4)
        private String permission;
    }
    
    

    排查经过:

    • 修改代码中映射的Java bean对象中属性的位置,发现还是为空
    • 将文件中的 主键列和姓名列交换位置,发现变成了姓名列读取的数据为空,主键列能正常读取到内容。

    问题现象描述分析:代码逻辑上目前没有问题,导入的数据有的列读取值为空,和CSV的第一列有关。即,CSV文件的第一列在代码中读取时总是为空。

    解决方案

    定义一个方法,在opencsv解析文件之间,对文件流进行判断处理

        /**
         * 读取流中前面的字符,看是否有bom,如果有bom,将bom头先读掉丢弃
         *
         * @param in
         * @return
         * @throws IOException
         */
        public static InputStream handlerFileBOMHead(InputStream in) throws IOException {
    
            PushbackInputStream testin = new PushbackInputStream(in);
            int ch = testin.read();
            if (ch != 0xEF) {
                testin.unread(ch);
            } else if ((ch = testin.read()) != 0xBB) {
                testin.unread(ch);
                testin.unread(0xef);
            } else if ((ch = testin.read()) != 0xBF) {
                throw new IOException("错误的UTF-8格式文件");
            } else {
            }
            return testin;
        }
    
JAVA-技能点