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 自定义规则

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352