前言
上文介绍了如何使用Hutool生成和解析CSV文件以及CSV文件的特点,CSV文件优缺点如下;
优点包括:
- 格式简单:CSV文件采用纯文本格式存储数据,格式简单易懂。
- 可读性强:CSV文件中的数据可以被任何文本编辑器打开和编辑,可读性强。
- 可以被广泛支持:CSV文件是一种常见的电子表格文件格式,在大多数操作系统和软件中都可以被支持。
缺点包括:
-
不支持复杂的数据类型:CSV文件只支持基本数据类型,对于复杂的数据类型如日期时间等需要进行额外的处理。
-
缺乏标准:由于CSV文件没有明确的标准,因此在处理CSV文件时需要注意一些细节,避免出现错误。
-
不适合大规模数据:当数据量很大时,CSV文件的读写性能会受到限制,不适合大规模数据的处理。
本文介绍通过使用Apache Commons生成和解析CSV文件。
Apache Commons CSV 工具类
Apache Commons CSV 是 Apache 软件基金会开发的一个 Java 库,提供了一组用于读取、写入和处理 CSV(逗号分隔值)格式数据的 API。使用 Apache Commons CSV 工具类可以简化 CSV 文件的生成和解析过程,提高开发效率。主要包含
CSVFormat
类、
CSVPrinter
类、
CSVParser
类以及
CSVRecord
类。
CSVFormat
类:
用于定义 CSV 文件的格式。它提供了一组常用的静态属性,用于快速定义 CSV 文件的格式,也可以通过构造函数自定义 CSV 文件的格式。
常用的静态属性有:
- CSVFormat.DEFAULT:默认 CSV 格式,逗号作为字段分隔符,双引号用于转义包含特殊字符的字段。
- CSVFormat.EXCEL:Excel 格式,逗号作为字段分隔符,双引号用于转义包含特殊字符的字段,行尾无需特殊处理。
- CSVFormat.TDF:制表符格式,制表符作为字段分隔符,双引号用于转义包含特殊字符的字段。
CSVPrinter
类:
用于生成 CSV 文件。它可以将数据写入到 CSV 文件中,并使用指定的格式进行格式化。
CSVParser
类:
用于解析 CSV 文件。它可以将 CSV 文件中的每一行数据解析为一个 CSVRecord 对象,通过该对象可以获取每个字段的值。
CSVRecord
类:
用于表示 CSV 文件中的一条记录。每个 CSVRecord 对象包含多个字段,可以通过索引或字段名获取每个字段的值。在 CSV 文件解析过程中,CSVRecord 对象会被用来存储每条记录的数据。
依赖如下:
🍊Maven
在项目的pom.xml的dependencies中加入以下内容:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.10.0</version>
</dependency>
🍐Gradle
implementation 'org.apache.commons:commons-csv:1.10.0'
CSV文件配置
Apache下的
CSVFormat
类根据常用CSV使用场景提供了CSV格式快速设置静态方法,如下表:
|
默认配置,使用逗号作为分隔符 |
---|---|
|
Excel 格式,使用逗号作为字段分隔符,双引号作为文本限定符,换行符使用 CRLF,允许空行 |
|
Informix 数据库导出格式,使用管道符号作为字段分隔符,单引号作为文本限定符,换行符使用系统默认的换行符。 |
|
Informix 数据库导出 CSV 格式,使用逗号作为字段分隔符,单引号作为文本限定符,换行符使用系统默认的换行符。 |
|
MongoDB 导出 CSV 格式,使用逗号作为字段分隔符,双引号作为文本限定符,换行符使用 LF。 |
|
MongoDB 导出 TSV 格式,使用制表符作为字段分隔符,双引号作为文本限定符,换行符使用 LF。 |
|
MySQL 导出 CSV 格式,使用逗号作为字段分隔符,双引号作为文本限定符,换行符使用 LF。 |
|
Oracle 数据库导出格式,使用逗号作为字段分隔符,双引号作为文本限定符,换行符使用 CRLF。 |
|
PostgreSQL 导出 CSV 格式,使用逗号作为字段分隔符,双引号作为文本限定符,换行符使用 LF。 |
|
PostgreSQL 导出文本格式,使用制表符作为字段分隔符,双引号作为文本限定符,换行符使用 CRLF。 |
|
符合 RFC4180 标准的 CSV 格式,使用逗号作为字段分隔符,双引号作为文本限定符,换行符使用 CRLF。 |
|
Tab-delimited 格式,使用制表符作为字段分隔符,双引号作为文本限定符,换行符使用系统默认的换行符。 |
这些静态CSVFormat设置是通过
CSVFormat
类中的静态代码块进行设置的,源代码如下,其中的不同可以参考源代码中的设置:
static {
DEFAULT = new CSVFormat(",", Constants.DOUBLE_QUOTE_CHAR, (QuoteMode)null, (Character)null, (Character)null, false, true, "\r\n", (String)null, (Object[])null, (String[])null, false, false, false, false, false, false, DuplicateHeaderMode.ALLOW_ALL);
EXCEL = DEFAULT.builder().setIgnoreEmptyLines(false).setAllowMissingColumnNames(true).build();
INFORMIX_UNLOAD = DEFAULT.builder().setDelimiter('|').setEscape('\\').setQuote(Constants.DOUBLE_QUOTE_CHAR).setRecordSeparator('\n').build();
INFORMIX_UNLOAD_CSV = DEFAULT.builder().setDelimiter(",").setQuote(Constants.DOUBLE_QUOTE_CHAR).setRecordSeparator('\n').build();
MONGODB_CSV = DEFAULT.builder().setDelimiter(",").setEscape(Constants.DOUBLE_QUOTE_CHAR).setQuote(Constants.DOUBLE_QUOTE_CHAR).setQuoteMode(QuoteMode.MINIMAL).setSkipHeaderRecord(false).build();
MONGODB_TSV = DEFAULT.builder().setDelimiter('\t').setEscape(Constants.DOUBLE_QUOTE_CHAR).setQuote(Constants.DOUBLE_QUOTE_CHAR).setQuoteMode(QuoteMode.MINIMAL).setSkipHeaderRecord(false).build();
MYSQL = DEFAULT.builder().setDelimiter('\t').setEscape('\\').setIgnoreEmptyLines(false).setQuote((Character)null).setRecordSeparator('\n').setNullString("\\N").setQuoteMode(QuoteMode.ALL_NON_NULL).build();
ORACLE = DEFAULT.builder().setDelimiter(",").setEscape('\\').setIgnoreEmptyLines(false).setQuote(Constants.DOUBLE_QUOTE_CHAR).setNullString("\\N").setTrim(true).setRecordSeparator(System.lineSeparator()).setQuoteMode(QuoteMode.MINIMAL).build();
POSTGRESQL_CSV = DEFAULT.builder().setDelimiter(",").setEscape((Character)null).setIgnoreEmptyLines(false).setQuote(Constants.DOUBLE_QUOTE_CHAR).setRecordSeparator('\n').setNullString("").setQuoteMode(QuoteMode.ALL_NON_NULL).build();
POSTGRESQL_TEXT = DEFAULT.builder().setDelimiter('\t').setEscape('\\').setIgnoreEmptyLines(false).setQuote((Character)null).setRecordSeparator('\n').setNullString("\\N").setQuoteMode(QuoteMode.ALL_NON_NULL).build();
RFC4180 = DEFAULT.builder().setIgnoreEmptyLines(false).build();
TDF = DEFAULT.builder().setDelimiter('\t').setIgnoreSurroundingSpaces(true).build();
}
添加一个CSVFormat设置的方法,用于设置CSV的格式实现定制版的CSV格式,代码如下:
/**
* 自定义CSV配置
* @return CSVFormat
*/
public static CSVFormat customCsvFormat(){
return CSVFormat.Builder.create()
.setAllowMissingColumnNames(false) // 不允许丢失字段名称,默认为True
.setDelimiter("|+|") // 自定义数据字段为|+|替换默认的逗号
.setHeader(CSV_HEAD) // CSV 表头
.setTrim(true) // 去除数据两边的空格,如 "Abc " 则实际输出为"Abc",但是数据为"A bc",实际输出还是"A bc"
.build();
}
注意
:
CSVFormat.DEFAULT.withHeader() 方法已经过时了,建议通过Build进行设置
生成CSV文件
通过使用
CSVPrinter
类可以很方便的将数据写入CSV文件,对比使用不同的CSV格式配置文件生成CSV文件,数据借助HuTool生成CSV中的generateUserList方法生成User列表,使用
CSVPrinter
代码如下:
/**
* 生成CSV文件
* @param users 数据来源
* @param csvFormat CSV 文件格式设置
*/
public static void generateCsvWithConfig(List<User> users, CSVFormat csvFormat){
// 可以通过设置FileWriter的编码来控制输出文件的编码格式
// FileWriter fileWriter = new FileWriter("ApacheCsv.csv", StandardCharsets.UTF_8);
try(FileWriter fileWriter = new FileWriter("ApacheCsv.csv");
CSVPrinter csvPrinter = new CSVPrinter(fileWriter, csvFormat)){
// 会将整个User 列表作为一条数据行写入
// csvPrinter.printRecord(users);
// 默认配置不会写表头,如果需要添加表头可以单独设置表头
// CSVFormat.DEFAULT.withHeader() 方法已经过时,通过Build进行设置
for (User user : users) {
csvPrinter.printRecord(user.getId(), user.getName(), user.getGender());
}
// 输出一个空行
csvPrinter.println();
csvPrinter.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
生成User list代码稍微修改一下,将名字中加入空格以检测自定义配置中setTrim方法,generateUserList方法代码如下:
/**
* 生成User列表
* @return List<User>
*/
public static List<User> generateUserList(){
List<User> dataList = new ArrayList<>();
dataList.add(new User(1, "张 三", "男"));
dataList.add(new User(2, "李四 ", "女"));
dataList.add(new User(3, "王五", "男"));
dataList.add(new User(4, "", "男"));
dataList.add(new User(5, "王五", null));
dataList.add(new User(3, null, "男"));
return dataList;
}
添加一个单元测试生成默认配置的CSV文件和自定义配置的CSV文件,代码如下:
@Test
void testApacheGenerateCsv(){
// 使用自定义的CSV设置
ApacheCsvUtil.generateCsvWithConfig(HutooCsvUtil.generateUserList(), ApacheCsvUtil.customCsvFormat());
// 使用默认的CSV设置
ApacheCsvUtil.generateCsvWithConfig(HutooCsvUtil.generateUserList(), CSVFormat.DEFAULT);
}
使用自定义配置生成的CSV文件如下,数据字段分隔符为|+| 且含有数据表头,同时姓名为“李四 ”的数据输出时去掉了数据右侧的空格,符合预期。
使用默认配置生成的CSV文件如下,数据字段分隔符为逗号,不包含表头且姓名为“李四 ”的数据输出时不仅没有去掉空格而且还多了双引号,在使用默认配置时需要注意。
解析CSV文件
通过使用
CSVParser
类可以解析CSV文件,该类也需要设定好CSV文件的格式,可以复用上面的CSV格式化配置,解析CSV文件数据通过
CSVRecord
接受数据,可以通过获取某列取值从而将CSV文件数据重新转换为User对象,解析CSV文件方法代码如下:
/**
* 解析CSV文件
* @param csvFormat CSV格式设置
* @return List<User>
*/
public static List<User> readCsv(CSVFormat csvFormat){
List<User> users = new ArrayList<>();
try(FileReader fileReader = new FileReader("ApacheCsv.csv");
CSVParser csvParser = new CSVParser(fileReader, csvFormat)){
csvParser.getRecords().forEach( csvRecord ->{
System.out.println(csvRecord.toString());
Integer id = Integer.valueOf(csvRecord.get(0));
String name = csvRecord.get(1);
String gender = csvRecord.get(2);
User user = new User(id, name, gender);
users.add(user);
});
users.forEach(System.out::println);
}catch (IOException e) {
e.printStackTrace();
}
return users;
}
读取上面使用自定义配置的CSV文件需要注意的是该CSV文件中包含了表头,表头数据无法转换成User数据,在读取的时候需要跳过表头,而如果直接使用
customCsvFormat
则会产生表头无法读取的问题,所以添加新的读取自定义CSV格式化文件的配置,代码如下:
/**
* 自定义CSV读取配置
* @return CSVFormat
*/
public static CSVFormat readCustomCsvFormat(){
return CSVFormat.Builder.create()
.setDelimiter("|+|") // 自定义数据字段为|+|替换默认的逗号
.setTrim(true) // 去除数据两边的空格,如 "Abc " 则实际输出为"Abc",但是数据为"A bc",实际输出还是"A bc"
.setIgnoreEmptyLines(true) // 忽略空行
.setHeader(CSV_HEAD) // CSV 表头
.setSkipHeaderRecord(true) // 跳过表头(需要设置表头后生效)
.build();
}
读取使用不同配置生成的CSV文件,通过转换后的User对象条数判定是否成功,单元测试如下:
@Test
void testApacheCsv(){
// 生成默认格式化的CSV文件
ApacheCsvUtil.generateCsvWithConfig(HutooCsvUtil.generateUserList(), CSVFormat.DEFAULT);
// 解析读取默认格式化的CSV文件
List<User> users = ApacheCsvUtil.readCsv(CSVFormat.DEFAULT);
assertEquals(6, users.size());
System.out.println("==========================");
// 生成自定义格式化的CSV文件
ApacheCsvUtil.generateCsvWithConfig(HutooCsvUtil.generateUserList(), ApacheCsvUtil.customCsvFormat());
// 解析读取自定义格式化的CSV文件
List<User> userList = ApacheCsvUtil.readCsv(ApacheCsvUtil.readCustomCsvFormat());
assertEquals(6, userList.size());
}
测试结果如下:
CSVRecord [comment='null', recordNumber=1, values=[1, 张 三, 男]]
CSVRecord [comment='null', recordNumber=2, values=[2, 李四 , 女]]
CSVRecord [comment='null', recordNumber=3, values=[3, 王五, 男]]
CSVRecord [comment='null', recordNumber=4, values=[4, , 男]]
CSVRecord [comment='null', recordNumber=5, values=[5, 王五, ]]
CSVRecord [comment='null', recordNumber=6, values=[3, , 男]]
User(id=1, name=张 三, gender=男)
User(id=2, name=李四 , gender=女)
User(id=3, name=王五, gender=男)
User(id=4, name=, gender=男)
User(id=5, name=王五, gender=)
User(id=3, name=, gender=男)
==========================
CSVRecord [comment='null', recordNumber=1, values=[1, 张 三, 男]]
CSVRecord [comment='null', recordNumber=2, values=[2, 李四, 女]]
CSVRecord [comment='null', recordNumber=3, values=[3, 王五, 男]]
CSVRecord [comment='null', recordNumber=4, values=[4, , 男]]
CSVRecord [comment='null', recordNumber=5, values=[5, 王五, ]]
CSVRecord [comment='null', recordNumber=6, values=[3, , 男]]
User(id=1, name=张 三, gender=男)
User(id=2, name=李四, gender=女)
User(id=3, name=王五, gender=男)
User(id=4, name=, gender=男)
User(id=5, name=王五, gender=)
User(id=3, name=, gender=男)
Process finished with exit code 0
注意
:
使用自定生成CVS格式配置生成的CSV文件如果通过设置了setHeader(CSV_HEAD)和setSkipHeaderRecord(true)方法时生成的CSV文件不会包含表头;读取CSV文件跳过表头时需要在指定了表头以后设置setSkipHeaderRecord(true)方才生效。
总结
在生成和解析 CSV 文件的过程中,使用 Apache Commons CSV 和 Hutool 工具都可以达到很好的效果,二者各有特点。
Apache Commons CSV 工具类的优点在于它是一个成熟、稳定的开源库,拥有广泛的社区支持和文档,提供了丰富的配置选项和灵活的 API 接口,可以满足复杂的 CSV 文件生成和解析需求。其缺点是使用起来相对繁琐,需要熟悉其 API 接口以及配置选项,对于简单的 CSV 文件操作可能会显得过于复杂。
Hutool 工具类的优点在于使用起来非常简单便捷,对于不熟悉 Apache Commons CSV 工具类的开发者来说,Hutool 可以快速实现 CSV 文件的生成和解析,并且代码简洁易懂。其缺点是对于复杂的 CSV 文件操作,Hutool 的灵活性不如 Apache Commons CSV 工具类,可能会存在一些限制。
综合来看,如果需要处理大量、复杂的 CSV 文件,并且有丰富的时间去研究和使用 Apache Commons CSV 工具类,那么它是更好的选择。如果只是需要快速实现简单的 CSV 文件操作,并且对于代码简洁易懂有较高的要求,那么可以选择使用 Hutool 工具类。