csv文本處理利器univocity-parsers介紹

univocity-parsers簡(jiǎn)介

工作中經(jīng)常會(huì)遇到需要導(dǎo)出或者解析csv的需求滥酥,Java中處理csv的開源庫(kù)也有很多淋纲,本文主要介紹通過univocity-parsers來解析和生成csv菇夸,univocity-parsers的github地址見此,在寫這篇文章的時(shí)候univocity-parsers 最新版為2.6.3

注: 本文所有例子源碼在都在github上堪澎。

使用詳解

在詳解介紹之前麦萤,我們先通過一個(gè)簡(jiǎn)單的例子來看看如何使用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內(nèi)容
            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內(nèi)容
            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內(nèi)容本鸣。Parsed可以標(biāo)記屬性和header之間的對(duì)應(yīng)關(guān)系,而Processor負(fù)責(zé)處理這兩者之間的映射硅蹦。

生成csv文本

setting介紹

從上面的例子可以看出荣德,CsvWriterSettings用來進(jìn)行輸出的一些配置。

    // Format接口童芹,這里使用的CsvFormat涮瞻,下面對(duì)CsvFormat詳細(xì)介紹
    private F format;

    // 默認(rèn)的nullValue,輸出的屬性的如果是null假褪,則使用這個(gè)值進(jìn)行輸出
    private String nullValue = null;

    // 一個(gè)列最大字符長(zhǎng)度
    private int maxCharsPerColumn = 4096;

    // 最多列數(shù)
    private int maxColumns = 512;

    // 是否跳過空行署咽,例如輸出的時(shí)候如果對(duì)應(yīng)的object是null,如果是true,則跳過
    private boolean skipEmptyLines = true;

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

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

    /** 
    可以配置一些對(duì)屬性的篩選
    ExcludeFieldNameSelector(excludeFields): 通過屬性的名字來忽略一些屬性的輸出
    FieldNameSelector(selectFields): 通過屬性的名字來選擇只輸出一些屬性
    這里其他對(duì)FieldSelector的實(shí)現(xiàn)
    **/
    private FieldSelector fieldSelector = null;

    //
    private boolean autoConfigurationEnabled = true;

    // 異常處理
    private ProcessorErrorHandler<? extends Context> errorHandler;

    // 配置出現(xiàn)異常的時(shí)候error meesage寫入到內(nèi)容的長(zhǎng)度
    private int errorContentLength = -1;

    // 是否跳過bits當(dāng)做空格
    private boolean skipBitsAsWhitespace = true;

    /**
    這個(gè)是關(guān)鍵部分宁否,例如我們剛才使用的BeanWriterProcessor窒升,是通過Bean的方式輸入
    也可以自己實(shí)現(xiàn)這個(gè)借口
    **/
    private RowWriterProcessor<?> rowWriterProcessor;

    // 如果設(shè)置成true,在寫入第一行的數(shù)據(jù)的時(shí)候慕匠,如果headers設(shè)置了則會(huì)自動(dòng)先寫入header
    private Boolean headerWritingEnabled = null;

    // 如果寫入了一個(gè)empty的string可以用這個(gè)值代替
    private String emptyValue = "";

    private boolean expandIncompleteRows = false;

    private boolean columnReorderingEnabled = false;

    // headers的配置饱须,可以調(diào)用writer的writeHeaders方法進(jìn)行寫入header的操作
    private String[] headers;

    //
    private boolean escapeUnquotedValues = false;

    // 是否通過fortmat配置的quote符號(hào),所有的是否加上quote符號(hào)台谊,如果設(shè)置成true蓉媳,默認(rèn)配置符號(hào)是", 測(cè)原來列內(nèi)容為xxx,變成"xxx"
    private boolean quoteAllFields = false;

    // 
    private boolean isInputEscaped = false;

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

    // 如果設(shè)置成true, 如果內(nèi)容 My "precious",則變成 "My ""precious"""
    private boolean quoteEscapingEnabled = false;
</code></pre>

<h3>format介紹</h3>

<pre><code class="language-java ">    // 換行符锅铅,默認(rèn)為 \n
    private static final String systemLineSeparatorString;
    private static final char[] systemLineSeparator;

    // 引用符號(hào)
    private char quote = '"';
    // 轉(zhuǎn)義符號(hào)
    private char quoteEscape = '"';
    // 分割符酪呻,默認(rèn)為,
    private char delimiter = ',';

    private Character charToEscapeQuoteEscaping = null;

通過一個(gè)簡(jiǎn)單的例子來看看改變fortmat的結(jié)果

@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@這一部分因?yàn)?code>setQuoteAllFields設(shè)置為true盐须,則前后加上了@
|設(shè)置成了分割符, 替換了原來的,
@*@testUser@因?yàn)槔锩嬗蠤玩荠,則使用QuoteEscape來進(jìn)行轉(zhuǎn)義,經(jīng)常遇到需要用\進(jìn)行轉(zhuǎn)義

注解的使用

有時(shí)候需要對(duì)輸出的文本進(jìn)行一些處理,例如有時(shí)候如果字段對(duì)應(yīng)的數(shù)字太長(zhǎng)丰歌,用excel打開csv文件的時(shí)候姨蟋,會(huì)被轉(zhuǎn)成科學(xué)計(jì)數(shù)法,這個(gè)時(shí)候可能需要對(duì)輸出的字段進(jìn)行一些處理

@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內(nèi)容
            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的注解使用立帖,自定義一個(gè)convert眼溶,重寫revert方法,可以對(duì)輸出的內(nèi)容進(jìn)行一些處理
通過上面自定義的處理之后晓勇,用excel打開文本堂飞,userNumber字段不會(huì)轉(zhuǎn)成科學(xué)計(jì)數(shù)法

注: execute對(duì)應(yīng)的方法是解析的時(shí)候。

解析csv文本

通過上面對(duì)生成的介紹绑咱,在解析時(shí)候很多的配置也是同樣如此绰筛,只不過是通過CsvParserSettingsCsvParser去實(shí)現(xiàn)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市描融,隨后出現(xiàn)的幾起案子铝噩,更是在濱河造成了極大的恐慌,老刑警劉巖窿克,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骏庸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡年叮,警方通過查閱死者的電腦和手機(jī)具被,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來只损,“玉大人一姿,你說我怎么就攤上這事。” “怎么了叮叹?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵艾栋,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我衬横,道長(zhǎng)裹粤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任蜂林,我火速辦了婚禮遥诉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘噪叙。我一直安慰自己矮锈,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布睁蕾。 她就那樣靜靜地躺著苞笨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪子眶。 梳的紋絲不亂的頭發(fā)上瀑凝,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音臭杰,去河邊找鬼粤咪。 笑死,一個(gè)胖子當(dāng)著我的面吹牛渴杆,可吹牛的內(nèi)容都是我干的寥枝。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼磁奖,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼囊拜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起比搭,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤冠跷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后身诺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蔽莱,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年戚长,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怠苔。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡同廉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情迫肖,我是刑警寧澤锅劝,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站蟆湖,受9級(jí)特大地震影響故爵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜隅津,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一诬垂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伦仍,春花似錦结窘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至谓苟,卻和暖如春官脓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背涝焙。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工卑笨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纱皆。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓湾趾,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親派草。 傳聞我的和親對(duì)象是個(gè)殘疾皇子搀缠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 一. Java基礎(chǔ)部分.................................................
    wy_sure閱讀 3,811評(píng)論 0 11
  • 上一篇我們講解了ButterKnife的設(shè)計(jì)思想,理解了ButterKnife綁定相關(guān)源碼的實(shí)現(xiàn)邏輯近迁。但是它是怎么...
    Ihesong閱讀 1,004評(píng)論 0 2
  • 1. 簡(jiǎn)介 1.1 什么是 MyBatis 艺普? MyBatis 是支持定制化 SQL、存儲(chǔ)過程以及高級(jí)映射的優(yōu)秀的...
    笨鳥慢飛閱讀 5,523評(píng)論 0 4
  • 點(diǎn)擊查看原文 Web SDK 開發(fā)手冊(cè) SDK 概述 網(wǎng)易云信 SDK 為 Web 應(yīng)用提供一個(gè)完善的 IM 系統(tǒng)...
    layjoy閱讀 13,769評(píng)論 0 15
  • 整體Retrofit內(nèi)容如下: 1鉴竭、Retrofit解析1之前哨站——理解RESTful 2歧譬、Retrofit解析...
    隔壁老李頭閱讀 6,509評(píng)論 4 31