使用commons-csv读取并处理大文件csv数据
commons-csv搜到的都是一次性读入所有csv行数据进内存再处理,对于大文件这样操作肯定会把内存爆掉。查阅了下源码,发现CSVParser在使用迭代器时会按行读取数据而不是一次性加载所有,实测80M大小156000+条数据的csv文件能正常读取,下面是示例。
一、导入commons-csv工具包
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.10.0</version>
</dependency>
二、编写工具类
package com.example;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
public class CSVUtils {
// 批量插入,单次读取数据量
public static final int LIST_SIZE = 1000;
/**
* 按行读取csv文件并对csv行记录执行func操作
*
* @param csvFile csv文件
* @param func 回调函数,接收List {@link CSVRecord} 对象,大小取决于 {@link CSVUtils#LIST_SIZE}
* @throws IOException 文件IO异常
*/
public static void readCSV(File csvFile, Function<List<CSVRecord>, ?> func) throws IOException {
CSVFormat format = CSVFormat.Builder.create()
.setHeader() // 读取header作为csv的key,否则CSVRecord.get(headerName)会报错
.setSkipHeaderRecord(true) // 跳过第一行的列名,列名单独是文件的自行搜索CSVFormat构造
.build();
CSVParser parse = format.parse(new FileReader(csvFile));
Iterator<CSVRecord> csvRecordIterator = parse.iterator();
// 2023/12/08 更新:写demo时没注意,实际应当使用ConcurrentLinkedQueue同步队列保证线程安全
List<CSVRecord> list = new ArrayList<>(LIST_SIZE);
for (int i = 0; i < LIST_SIZE && csvRecordIterator.hasNext(); i++) {
list.add(csvRecordIterator.next());
if (i == LIST_SIZE - 1 && csvRecordIterator.hasNext()) {
i = -1;
func.apply(list); // 实际使用场景请使用线程池
list = new ArrayList<>(LIST_SIZE);
}
}
if (!list.isEmpty()) {
func.apply(list); // 实际使用场景请使用线程池
}
}
}
三、调用
public boolean batchSave(List<CSVRecord> csvRecordList) {
// 这里只演示读取字段,具体如何批量保存请参考多线程插入数据
for (CSVRecord csvRecord : csvRecordList) {
log.info(csvRecord.get("columnName")); // columnName: csv列名,如id
}
return true;
}
// 调用
public void loadCSV() throws IOException {
ClassPathResource csvResource = new ClassPathResource("CSV_FILE_PATH"); // CSV_FILE_PATH: csv文件路径
CSVUtils.readCSV(csvResource.getFile(), this::batchSave);
}