工作中经常会遇到需要导出或者解析csv的需求,Java中处理csv的开源库也有很多,本文主要介绍通过univocity-parsers
来解析和生成csv,univocity-parsers
源码存放于github,在写这篇文章的时候univocity-parsers
最新版为2.8.4
在详解介绍之前,我们先通过一个简单的例子来看看如何使用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
负责处理这两者之间的映射。
从上面的例子可以看出,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
对应的方法是解析的时候。
通过上面对生成的介绍,在解析时候很多的配置也是同样如此,只不过是通过CsvParserSettings
和CsvParser
去实现。
如下为我们对比 uniVocity-parsers 和 JavaCSV 的测试对比表:
文件大小 | JavaCSV解析耗时 | uniVocity-parsers解析耗时 |
10MB, 145453 行 | 1138ms | 836ms |
100MB, 809008 行 | 23s | 6s |
434MB, 4499959 行 | 91s | 28s |
1GB, 23803502 行 | 245s | 70s |
在这里可以查看几乎所有CSV解析库的性能对比分析表,从表中可以发现,uniVocity-parsers以绝对优势领先其他库。
uniVocity-parsers在性能和灵活性方面的优势得益于如下设计和机制: