当前位置: 首页 > 工具软件 > openCSV > 使用案例 >

java SprintBoot Opencsv处理csv文件, csv文件读取时第一列无法读取到的问题

吕新
2023-12-01

网上发现这篇文章,这里转载给发家分享一下。原文:https://www.jianshu.com/p/6414185b2f01

 

1. 什么叫做CSV

Comma-Separated Value ([卡门 赛婆乱提的]逗号分隔)(CSV),因分隔符没有严格的要求,可以使用逗号,也可以使用其他字符(如制表符\t,分号等),所以CSV也被称为逗号分隔或者其他字符分隔值。csv文件是使用纯文本来存储表格数据(只能存储文本,不能存储二进制)。

2. CSV解析的API方法

2.1. Maven依赖

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

2.2 CSVReader对象

 

CSVReader对象的构造方法

构造器涉及到的三个参数:

  1. reader:读取文件的流对象,常有的是BufferedReader,InputStreamReader。
  2. separator:用于定义前面提到的分割符,默认为逗号CSVWriter.DEFAULT_SEPARATOR用于分割各列。
  3. quotechar:[寇特]用于定义各个列的引号,有时候csv文件中会用引号或者其它符号将一个列引起来,例如一行可能是:"1","2","3",如果想读出的字符不包含引号,就可以把参数设为:"CSVWriter.NO_QUOTE_CHARACTER "

注:若是设置解析的编码,需要在InputStreamReader对象中设置。

 

定义一个以逗号为分隔符、读取时忽略引号的CSVReader就是:

CSVReader reader = new CSVReader(
new InputStreamReader(new FileInputStream(csvFile), "GBK"), 
CSVWriter.DEFAULT_SEPARATOR,
CSVWriter.NO_QUOTE_CHARACTER);

2.3 read方法

 

读取数据的read方法

  • readAll():读取全部;
  • readNext():读取一行;
    注意:如果先readNext,再readAll,readAll也是从readNext之后的那一行开始了,也就是readNext读了之后就不会再读了。

2.4 CsvWriter对象

 

    CSVWriter writer= null;
        try {
            writer = new CSVWriter(
                    new OutputStreamWriter(new FileOutputStream(fileName),"utf-8"),
                    '\t',CSVWriter.NO_QUOTE_CHARACTER);
            String[] strings={"第一行","001","sds"};
            writer.writeNext(strings);
            writer.flush();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

3. CSV新版本

上述的在Opencsv4.0版本以上已经废弃了。采用CSVReaderBuilder来代替。本质上是采用的建造者模式来构建对象,更加优雅。

3.1 构建CSVReader对象

     try {
            InputStreamReader is = new InputStreamReader(new FileInputStream(fileName), "utf-8");
            CSVParser csvParser = new CSVParserBuilder().withSeparator('\t').build();
            CSVReader reader = new CSVReaderBuilder(is).withCSVParser(csvParser).build();
            List<String[]> strings = reader.readAll();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

3.2 CsvToBeanBuilder

优雅的解析文档中的字段。将CSV文件转换为Bean对象。

在上面我们可以使用readNext或者readAll进行逐行解读。但是opencsv提供了基于"策略"的映射,将CSV绑定到bean。

 

策略简略

接口名策略
MappingStrategy顶级的映射接口
headerColumnNameMappingStrategy基于列名的映射策略,读取csv文件的第一行作为header,比如header1,header2,header3然后调用bean的setHeader1方法,setHeader2方法,setHeader3方法分别设置值。**所以这种策略要求,列名与bean中的属性名完全一致,如果不一致,则值为空,不会出错。使用注解时,注解名字必须与csv中列名一致。
ColumnPositionMappingStrategy列位置映射策略,他没有header的概念,所以会输出取所有行。在columnMapping数组中指定bean的属性,第一个值对应csv的第一列,第二个值对应csv的第二列……
HeaderColumnNameTranslateMappingStrategy列头名字翻译映射策略,与HeaderColumnNameMappintStrategy相比,bean的属性名可以与csv列头不一样。通过指定map来映射。

注:bean的类型只能为基本数据类型以及String类型,若是BigDecimal类型,那么将会抛出异常。

解析后的bean类:

public class Stu {
    private  String header1;
    private  String header2;
    private  String header3;
//省略getter、setter、tostring方法
}

csv文件:

header1,header2,header3
哈哈,你好,不错
第二行,不好,不是

基于列索引的映射

通俗点就是列位置映射,csv文件中列位置对应到bean中的列。
需要注意的是,该策略会输出所有的行,故,我们需要跳过某些行。

  • 非注解方式(使用数组)
    public static <T> List<T> parseCsvToBean(Class<T> clazz, String fileName, String params, char csvSeparator, int skipLineNum) {
        //获取列位置数组
        String[] columnMapping = params.split("\\|", -1);
        ColumnPositionMappingStrategy<T> mapper = new ColumnPositionMappingStrategy<>();
        mapper.setColumnMapping(columnMapping);
        mapper.setType(clazz);
        List<T> parse;
        try {
            CsvToBean csvToBean = new CsvToBeanBuilder(new FileReader(fileName))
                    .withMappingStrategy(mapper)
                    .withSeparator(csvSeparator)
                    .withSkipLines(skipLineNum)
                    .build();
            parse = csvToBean.parse();
        } catch (FileNotFoundException e) {
            logger.error("【fileName:{}地址异常!】", fileName);
            throw e;
        }
  }

测试方法:

    @Test
    public void test(){
        String fileName = "D:\\app\\share\\download\\20190605\\testOfComma.csv";
        String params="header1|header2|header3";
        List<Stu> stus = CsvUtil.parseCsvToBean(Stu.class, fileName, params, ',', 1);
        for (Stu stu:stus){
            System.out.println(stu);
        }
    }

测试结果:

Stu{header1='哈哈', header2='你好', header3='不错'}
Stu{header1='第二行', header2='不好', header3='不是'}
Process finished with exit code 0
  • 注解方式
public class Stu {

    @CsvBindByPosition(position = 0)
    private  String header1;

    @CsvBindByPosition(position = 1)
    private  String header2;

    @CsvBindByPosition(position = 2)
    private  String header3;
}

基于列名的映射

  • 非注解方式:
CSVReader reader = new CSVReader(new InputStreamReader(new FileInputStream("test.csv"),"gbk"));

      HeaderColumnNameMappingStrategy<SimpleBeanInfo> mapper = new
              HeaderColumnNameMappingStrategy<SimpleBeanInfo>();
      mapper.setType(SimpleBeanInfo.class);
      CsvToBean<SimpleBeanInfo>  csvToBean = new CsvToBean<SimpleBeanInfo>();
 
      List<SimpleBeanInfo> list = csvToBean.parse(mapper, reader);
 
      for(SimpleBeanInfo e : list){
          System.out.println(e);
}
  • 注解方式

csv文件:

编号  姓名  性别
001 李明  男
002 tom 男

 

public class Person {

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

    @CsvBindByName(column = "姓名")
    private  String name;

    @CsvBindByName(column = "编号")
    private String id;
}
//省略getter和setter方法

 

    @Test
    public void testHeaderColumn() throws IOException {
        String fileName = "D:\\app\\share\\eaglewood-client\\download\\20190605\\testofTab.csv";
        InputStreamReader is = new InputStreamReader(CsvUtil.getInputStream(new  FileInputStream(fileName)), "utf-8");
        HeaderColumnNameMappingStrategy<Person> mappingStrategy = new HeaderColumnNameMappingStrategy<>();
        mappingStrategy.setType(Person.class);
        CsvToBean<Person> build = new CsvToBeanBuilder<Person>(is).withMappingStrategy(mappingStrategy).withSeparator(',').build();
        List<Person> personList = build.parse();
        for(Person person:personList){
            System.out.println(person);
        }
    }

 /**
     * 读取流中前面的字符,看是否有bom,如果有bom,将bom头先读掉丢弃
     *  (opencsv 按列名获取bean对象,第一列缺失的情况)
     *  InputStreamReader is = new InputStreamReader(
     *  CsvUtil.getInputStream(new  FileInputStream(fileName)), "utf-8");
     * @param in
     * @return
     * @throws IOException
     */
    public static InputStream getInputStream(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;
    }

测试结果:

Person{id='001', sex='男', name='李明'}
Person{id='002', sex='男', name='tom'}

基于列名转换映射

 

CSVReader reader = new CSVReader(new InputStreamReader(new FileInputStream("test.csv"),"gbk"));
      /*
       * 基于列名转换,映射成类
      */
      HeaderColumnNameTranslateMappingStrategy<SimpleBeanInfo> mapper = 
              new HeaderColumnNameTranslateMappingStrategy<SimpleBeanInfo>();
      mapper.setType(SimpleBeanInfo.class);
 
      Map<String,String> columnMapping = new HashMap<String,String>();
      columnMapping.put("header1", "header1");//csv中的header1对应bean的header1
      columnMapping.put("header2", "header2");
      columnMapping.put("header3", "header3");
      mapper.setColumnMapping(columnMapping);
 
      CsvToBean<SimpleBeanInfo>  csvToBean = new CsvToBean<SimpleBeanInfo>();
 
      List<SimpleBeanInfo> list = csvToBean.parse(mapper, reader);
 
      for(SimpleBeanInfo e : list){
          System.out.println(e);
}

4. 转换器

在csv获取的都是字符串,这种情况下应该使用转换器。将csv中的字段转换为对应的bean中的字段类型。

public class Cluster {

       @CsvBindByName
       private String cluster;

       @CsvCustomBindByName(converter = ConvertSplitOnWhitespace.class)
       private String[] nodes;

       @CsvCustomBindByName(converter = ConvertGermanToBoolean.class)
       private boolean production;

       // Getters and setters go here.
     }

opencsv为我们提供了上面的两个转换器(我们可以参考,来实现自定义转换器)。使用AbstractBeanField<T>类来实现转换器。

csv文件

编号,姓名,性别,金额
001,李明,男,0.23
002,tom,男,3.00

 

public class ConvertGermanToBigDecimal<Person> extends AbstractBeanField<Person> {

    @Override
    protected Object convert(String value) throws CsvDataTypeMismatchException, CsvConstraintViolationException {
        Field field = getField();
        System.out.println(field);
        return new BigDecimal(value);
    }
}

注:若是列映射策略,则要使用@CsvCustomBindByPosition()注解。

public class Person {


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

    @CsvBindByName(column = "姓名")
    private  String name;

    @CsvBindByName(column = "编号")
    private String id;

    @CsvCustomBindByName(column = "金额",converter = ConvertGermanToBigDecimal.class)
    private BigDecimal amount;
}

测试结果:

Person{sex='男', name='李明', id='001', amount=0.23}
Person{sex='男', name='tom', id='002', amount=3.00}

5. 过滤器

opencsv提供了过滤器,可以过滤某些行,比如page header、page footer等

所有的过滤器必须实现CsvToBeanFilter 接口

public class MyCsvToBeanFilter implements CsvToBeanFilter {
    @Override
    public boolean allowLine(String[] line) {
        //若是第二列为tom,则过滤掉
        if ("tom".equals(line[1])) {
            return false;
        }
        return true;
    }
}

 

 @Test
    public void testHeaderColumn() throws IOException {
        String fileName = "D:\\app\\share\\download\\20190605\\testofTab.csv";
        InputStreamReader is = new InputStreamReader(CsvUtil.getInputStream(new  FileInputStream(fileName)), "utf-8");
        HeaderColumnNameMappingStrategy<Person> mappingStrategy = new HeaderColumnNameMappingStrategy<>();
        mappingStrategy.setType(Person.class);
        CsvToBean<Person> build = new CsvToBeanBuilder<Person>(is)
                .withMappingStrategy(mappingStrategy)
                .withFilter(new MyCsvToBeanFilter()) //增加过滤器,将tom过滤掉
                .withSeparator(',')
                .build();
        List<Person> personList = build.parse();
        for(Person person:personList){
            System.out.println(person);
        }
    }

测试结果

Person{sex='男', name='李明', id='001', amount=0.23}

6. 解决csv文件读取时第一列无法读取到的问题

在使用opencsv做csv文件导出与导入的时候,由于导入的csv文件带有bom标识,导致老是文件的第一列无法读取,下面贴出解决方法:

症状:

使用opencsv进行文件读取时,无法读取到第一列的内容

原因:

csv文件带有bom头

解决方法:

方法一:notepad++打开csv文件,在“编码”处选择“使用utf-8编码”,然后导入
方法二:在java后台去掉文件数据流中的bom头,去除方法如下:

/**
     * 读取流中前面的字符,看是否有bom,如果有bom,将bom头先读掉丢弃
     *
     * @param in
     * @return
     * @throws IOException
     */
    public static InputStream getInputStream(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;
    }



然后进行读取csv文件的代码:   
 

// 此处的file为multipartfile格式
    try {
            InputStream inputStream = getInputStream(file.getInputStream());
            InputStreamReader reader = new InputStreamReader(inputStream, Charset.forName("UTF-8"));

            HeaderColumnNameMappingStrategy strategy = new HeaderColumnNameMappingStrategy();
            strategy.setType(TestDTO.class);
            if(null != reader) {
                CsvToBean csvToBean = new CsvToBeanBuilder<TestDTO>(reader).withMappingStrategy(strategy).build();
                List<TestDTO> testDTOLists = csvToBean.parse();
                for(int i=0;i<testDTOLists.size();i++){
                    System.out.println("testDTOList======"+testDTOLists);
                }
                reader.close();
                return true;
            }
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }

 

文章参考:

https://www.jianshu.com/p/6414185b2f01

https://blog.csdn.net/dushu990/article/details/80509968

 类似资料: