csv文件處理——Opencsv

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.png
writer.png
    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}

文章參考:

(官網)csv官方文檔

(官網)CSVParserBuilder——CSVParser的目的是獲取單個字符串并根據分隔符,引號和轉義字符將其解析為其元素。

(官網)CSVReaderBuilder類

(理論)原CSV類庫:OpenCSV

(API)用opencsv文件讀寫CSV文件

(API)opencsv4.0 自定義規則

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。