csv文本处理利器univocity-parsers介绍

univocity-parsers简介

工作中经常会遇到需要导出或者解析csv的需求,Java中处理csv的开源库也有很多,本文主要介绍通过univocity-parsers来解析和生成csv,univocity-parsers的github地址见此,在写这篇文章的时候univocity-parsers 最新版为2.6.3

注: 本文所有例子源码在都在github上。

使用详解

在详解介绍之前,我们先通过一个简单的例子来看看如何使用univocity-parsers

@Slf4j
public class HowToUse {

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Student {

        @Parsed(field = "userNumber")
        private String userNumber;

        @Parsed(field = "userName")
        private String userName;

        @Parsed(field = "age")
        private Integer age;

    }

    public static final String[] HEADERS = new String[]{"userNumber", "userName", "age"};

    @Test
    public void howToUse() throws IOException {
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {

            // 生成CSV内容
            Student student = new Student("1111111111111111111111", "testUser", 20);
            final CsvWriterSettings csvWriterSettings = new CsvWriterSettings();
            csvWriterSettings.setHeaderWritingEnabled(Boolean.TRUE);
            csvWriterSettings.setHeaders(HEADERS);
            csvWriterSettings.setRowWriterProcessor(new BeanWriterProcessor<>(Student.class));
            CsvWriter writer = new CsvWriter(outputStream, csvWriterSettings);
            writer.processRecord(student);
            writer.close();

            final byte[] out = outputStream.toByteArray();
            log.info("output: {}", new String(out));


            // 解析CSV内容
            CsvParserSettings csvParserSettings = new CsvParserSettings();
            final BeanListProcessor beanListProcessor = new BeanListProcessor(Student.class);
            csvParserSettings.setProcessor(beanListProcessor);
            CsvParser csvParser = new CsvParser(csvParserSettings);
            csvParser.parse(new ByteArrayInputStream(out));

            final List<Student> students = beanListProcessor.getBeans();
            final String[] headers = beanListProcessor.getHeaders();
            log.info("headers: {}", String.join(",", headers));
            log.info("students: {}", students.toString());

        }
    }
}
 - output: userNumber,userName,age
1111111111111111111111,testUser,20

- headers: userNumber,userName,age
- students: [HowToUse.Student(userNumber=1111111111111111111111, userName=testUser, age=20)]

这里可以看到,基于注解能够很快的生成和解析CSV内容。Parsed可以标记属性和header之间的对应关系,而Processor负责处理这两者之间的映射。

生成csv文本

setting介绍

从上面的例子可以看出,CsvWriterSettings用来进行输出的一些配置。

    // Format接口,这里使用的CsvFormat,下面对CsvFormat详细介绍
    private F format;

    // 默认的nullValue,输出的属性的如果是null,则使用这个值进行输出
    private String nullValue = null;

    // 一个列最大字符长度
    private int maxCharsPerColumn = 4096;

    // 最多列数
    private int maxColumns = 512;

    // 是否跳过空行,例如输出的时候如果对应的object是null,如果是true,则跳过
    private boolean skipEmptyLines = true;

    // 是否跳过尾部的空格
    private boolean ignoreTrailingWhitespaces = true;

    // 是否跳过首部的空格
    private boolean ignoreLeadingWhitespaces = true;

    /** 
    可以配置一些对属性的筛选
    ExcludeFieldNameSelector(excludeFields): 通过属性的名字来忽略一些属性的输出
    FieldNameSelector(selectFields): 通过属性的名字来选择只输出一些属性
    这里其他对FieldSelector的实现
    **/
    private FieldSelector fieldSelector = null;

    //
    private boolean autoConfigurationEnabled = true;

    // 异常处理
    private ProcessorErrorHandler<? extends Context> errorHandler;

    // 配置出现异常的时候error meesage写入到内容的长度
    private int errorContentLength = -1;

    // 是否跳过bits当做空格
    private boolean skipBitsAsWhitespace = true;

    /**
    这个是关键部分,例如我们刚才使用的BeanWriterProcessor,是通过Bean的方式输入
    也可以自己实现这个借口
    **/
    private RowWriterProcessor<?> rowWriterProcessor;

    // 如果设置成true,在写入第一行的数据的时候,如果headers设置了则会自动先写入header
    private Boolean headerWritingEnabled = null;

    // 如果写入了一个empty的string可以用这个值代替
    private String emptyValue = "";

    private boolean expandIncompleteRows = false;

    private boolean columnReorderingEnabled = false;

    // headers的配置,可以调用writer的writeHeaders方法进行写入header的操作
    private String[] headers;

    //
    private boolean escapeUnquotedValues = false;

    // 是否通过fortmat配置的quote符号,所有的是否加上quote符号,如果设置成true,默认配置符号是", 测原来列内容为xxx,变成"xxx"
    private boolean quoteAllFields = false;

    // 
    private boolean isInputEscaped = false;

    private boolean normalizeLineEndingsWithinQuotes = true;
    private char[] quotationTriggers = new char[0];

    // 如果设置成true, 如果内容 My "precious",则变成 "My ""precious"""
    private boolean quoteEscapingEnabled = false;
</code></pre>

<h3>format介绍</h3>

<pre><code class="language-java ">    // 换行符,默认为 \n
    private static final String systemLineSeparatorString;
    private static final char[] systemLineSeparator;

    // 引用符号
    private char quote = '"';
    // 转义符号
    private char quoteEscape = '"';
    // 分割符,默认为,
    private char delimiter = ',';

    private Character charToEscapeQuoteEscaping = null;

通过一个简单的例子来看看改变fortmat的结果

@Test
    public void excludeFields() throws IOException {
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            Student student = new Student("1111111111111111111111", "@testUser", 20);

            CsvFormat csvFormat = new CsvFormat();
            csvFormat.setQuote('@');
            csvFormat.setQuoteEscape('*');
            csvFormat.setDelimiter('|');

            final CsvWriterSettings csvWriterSettings = new CsvWriterSettings();
            csvWriterSettings.setHeaderWritingEnabled(Boolean.TRUE);
            csvWriterSettings.setQuoteAllFields(true);
            csvWriterSettings.setFormat(csvFormat);
            csvWriterSettings.setQuoteEscapingEnabled(true);
            csvWriterSettings.setHeaders(HEADERS);
            csvWriterSettings.setRowWriterProcessor(new BeanWriterProcessor<>(Student.class));
            CsvWriter writer = new CsvWriter(outputStream, csvWriterSettings);
            writer.processRecord(student);
            writer.close();

            final byte[] out = outputStream.toByteArray();
            log.info("output: {}", new String(out));
        }
    }
- output: @userNumber@|@userName@|@age@
@1111111111111111111111@|@*@testUser@|@20@

@1111111111111111111111@这一部分因为setQuoteAllFields设置为true,则前后加上了@
|设置成了分割符, 替换了原来的,
@*@testUser@因为里面有@,则使用QuoteEscape来进行转义,经常遇到需要用\进行转义

注解的使用

有时候需要对输出的文本进行一些处理,例如有时候如果字段对应的数字太长,用excel打开csv文件的时候,会被转成科学计数法,这个时候可能需要对输出的字段进行一些处理

@Slf4j
public class AnnotationTest {

    @AllArgsConstructor
    @NoArgsConstructor
    public static class Student {

        @Parsed(field = "userNumber")
        @Convert(conversionClass = HumanReadableStringOutputConvert.class)
        private String userNumber;

        @Parsed(field = "userName")
        private String userName;

        @Parsed(field = "age")
        private Integer age;
    }

    public static class HumanReadableStringOutputConvert implements Conversion<String, String> {

        private String prefix;

        private String suffix;

        public HumanReadableStringOutputConvert(String... args) {
            String defaultPrefix = "=\"";
            String defaultSuffix = "\"";
            final int length = args.length;
            if (length >= 1) {
                defaultPrefix = args[0];
            }

            if (length >= 2) {
                defaultSuffix = args[1];
            }

            this.prefix = defaultPrefix;
            this.suffix = defaultSuffix;

        }

        @Override
        public String execute(String input) {
            return null;
        }

        @Override
        public String revert(String input) {
            if (input == null) {
                return input;
            }
            return prefix + input + suffix;
        }
    }

    @Test
    public void name() throws IOException {
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {

            // 生成CSV内容
            Student student = new Student("1111111111111111111111", "testUser", 20);
            final CsvWriterSettings csvWriterSettings = new CsvWriterSettings();
            csvWriterSettings.setHeaderWritingEnabled(Boolean.TRUE);
            csvWriterSettings.setHeaders(HEADERS);
            csvWriterSettings.setRowWriterProcessor(new BeanWriterProcessor<>(Student.class));
            CsvWriter writer = new CsvWriter(outputStream, csvWriterSettings);
            writer.processRecord(student);
            writer.close();

            final byte[] out = outputStream.toByteArray();
            log.info("output: {}", new String(out));
        }
    }
}
21:44:03.707 [main] INFO space.chaoluo.univocity.generate.AnnotationTest - output: userNumber,userName,age
="1111111111111111111111",testUser,20

通过Convert的注解使用,自定义一个convert,重写revert方法,可以对输出的内容进行一些处理
通过上面自定义的处理之后,用excel打开文本,userNumber字段不会转成科学计数法

注: execute对应的方法是解析的时候。

解析csv文本

通过上面对生成的介绍,在解析时候很多的配置也是同样如此,只不过是通过CsvParserSettingsCsvParser去实现

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

推荐阅读更多精彩内容

  • 一. Java基础部分.................................................
    wy_sure阅读 3,774评论 0 11
  • 上一篇我们讲解了ButterKnife的设计思想,理解了ButterKnife绑定相关源码的实现逻辑。但是它是怎么...
    Ihesong阅读 987评论 0 2
  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 5,398评论 0 4
  • 点击查看原文 Web SDK 开发手册 SDK 概述 网易云信 SDK 为 Web 应用提供一个完善的 IM 系统...
    layjoy阅读 13,590评论 0 15
  • 整体Retrofit内容如下: 1、Retrofit解析1之前哨站——理解RESTful 2、Retrofit解析...
    隔壁老李头阅读 6,356评论 4 31