前言
上文介绍了如何使用Hutool生成和解析CSV文件以及CSV文件的特点,CSV文件优缺点如下;
优点包括:
- 格式简单:CSV文件采用纯文本格式存储数据,格式简单易懂。
- 可读性强:CSV文件中的数据可以被任何文本编辑器打开和编辑,可读性强。
- 可以被广泛支持:CSV文件是一种常见的电子表格文件格式,在大多数操作系统和软件中都可以被支持。
缺点包括:
-
不支持复杂的数据类型:CSV文件只支持基本数据类型,对于复杂的数据类型如日期时间等需要进行额外的处理。
-
缺乏标准:由于CSV文件没有明确的标准,因此在处理CSV文件时需要注意一些细节,避免出现错误。
-
不适合大规模数据:当数据量很大时,CSV文件的读写性能会受到限制,不适合大规模数据的处理。
本文介绍通过使用Apache ***mons生成和解析CSV文件。
Apache ***mons CSV 工具类
Apache ***mons CSV 是 Apache 软件基金会开发的一个 java 库,提供了一组用于读取、写入和处理 CSV(逗号分隔值)格式数据的 API。使用 Apache ***mons 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.***mons</groupId>
<artifactId>***mons-csv</artifactId>
<version>1.10.0</version>
</dependency>
🍐Gradle
implementation 'org.apache.***mons:***mons-csv:1.10.0'
CSV文件配置
Apache下的CSVFormat
类根据常用CSV使用场景提供了CSV格式快速设置静态方法,如下表:
DEFAULT |
默认配置,使用逗号作为分隔符 |
---|---|
EXCEL |
Excel 格式,使用逗号作为字段分隔符,双引号作为文本限定符,换行符使用 CRLF,允许空行 |
InformixUnload |
Informix 数据库导出格式,使用管道符号作为字段分隔符,单引号作为文本限定符,换行符使用系统默认的换行符。 |
InformixUnloadCsv |
Informix 数据库导出 CSV 格式,使用逗号作为字段分隔符,单引号作为文本限定符,换行符使用系统默认的换行符。 |
MongoDBCsv |
MongoDB 导出 CSV 格式,使用逗号作为字段分隔符,双引号作为文本限定符,换行符使用 LF。 |
MongoDBTsv |
MongoDB 导出 TSV 格式,使用制表符作为字段分隔符,双引号作为文本限定符,换行符使用 LF。 |
MySQL |
MySQL 导出 CSV 格式,使用逗号作为字段分隔符,双引号作为文本限定符,换行符使用 LF。 |
Oracle |
Oracle 数据库导出格式,使用逗号作为字段分隔符,双引号作为文本限定符,换行符使用 CRLF。 |
PostgreSQLCsv |
PostgreSQL 导出 CSV 格式,使用逗号作为字段分隔符,双引号作为文本限定符,换行符使用 LF。 |
PostgreSQLText |
PostgreSQL 导出文本格式,使用制表符作为字段分隔符,双引号作为文本限定符,换行符使用 CRLF。 |
RFC4180 |
符合 RFC4180 标准的 CSV 格式,使用逗号作为字段分隔符,双引号作为文本限定符,换行符使用 CRLF。 |
TDF |
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 [***ment='null', recordNumber=1, values=[1, 张 三, 男]]
CSVRecord [***ment='null', recordNumber=2, values=[2, 李四 , 女]]
CSVRecord [***ment='null', recordNumber=3, values=[3, 王五, 男]]
CSVRecord [***ment='null', recordNumber=4, values=[4, , 男]]
CSVRecord [***ment='null', recordNumber=5, values=[5, 王五, ]]
CSVRecord [***ment='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 [***ment='null', recordNumber=1, values=[1, 张 三, 男]]
CSVRecord [***ment='null', recordNumber=2, values=[2, 李四, 女]]
CSVRecord [***ment='null', recordNumber=3, values=[3, 王五, 男]]
CSVRecord [***ment='null', recordNumber=4, values=[4, , 男]]
CSVRecord [***ment='null', recordNumber=5, values=[5, 王五, ]]
CSVRecord [***ment='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 ***mons CSV 和 Hutool 工具都可以达到很好的效果,二者各有特点。
Apache ***mons CSV 工具类的优点在于它是一个成熟、稳定的开源库,拥有广泛的社区支持和文档,提供了丰富的配置选项和灵活的 API 接口,可以满足复杂的 CSV 文件生成和解析需求。其缺点是使用起来相对繁琐,需要熟悉其 API 接口以及配置选项,对于简单的 CSV 文件操作可能会显得过于复杂。
Hutool 工具类的优点在于使用起来非常简单便捷,对于不熟悉 Apache ***mons CSV 工具类的开发者来说,Hutool 可以快速实现 CSV 文件的生成和解析,并且代码简洁易懂。其缺点是对于复杂的 CSV 文件操作,Hutool 的灵活性不如 Apache ***mons CSV 工具类,可能会存在一些限制。
综合来看,如果需要处理大量、复杂的 CSV 文件,并且有丰富的时间去研究和使用 Apache ***mons CSV 工具类,那么它是更好的选择。如果只是需要快速实现简单的 CSV 文件操作,并且对于代码简洁易懂有较高的要求,那么可以选择使用 Hutool 工具类。
本文首发于香菜喵,打开微信随时随地读,文章下方 ↓ ↓ ↓