新的项目需要处理其他设备生成的csv文件,自己手写流读取结果整崩了,于是乎放弃,转而使用Apach最新的csv处理依赖
1.引入依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.13.0</version>
</dependency>
2.编写工具类
刚一开始用这个版本的依赖。会发现大部分CSVFormat原来的创建方法已经被废弃
作为优秀的开发人员肯定也不会使用废弃的方法,查看源代码后可以发现现在CSVFormat已经转为构筑式创建(原来也是,就是代码很丑陋),创建方式如下
csvFormat = CSVFormat.EXCEL
.builder()
//.setNullString("") //将空字符串转为null,方便跳过
.setTrim(true)
.setHeader() //读取表头(用于后面isMapping的判断)
.setSkipHeaderRecord(true) //跳过表头
.setIgnoreEmptyLines(true) //跳过空行
.get();
并且其还有自己默认实现的一些默认的构筑类型,有兴趣的可以自己看一下,我只处理csv文件所以使用EXCEL构筑
下面是我写的一个工具类,具体的可以自行调整,因为项目可能读取多个csv文件,并且每个文件都大概在30-60w数据之间,文件也算是非常的大了,我在这里是采取多线程异步处理的方式去对文件进行处理,具体的结合自己实际业务去分析写代码.
public class CsvTools<T> {
/**
* 单次读取行数
*/
public static final int READ_SIZE_ = 1000;
//获取核心线程数
private static final int CORE_POOL_SIZE_ = Runtime.getRuntime().availableProcessors();
private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(
CORE_POOL_SIZE_,
CORE_POOL_SIZE_ * 2,
60, TimeUnit.SECONDS, // 空闲线程的存活时间
new LinkedBlockingQueue<>(), // 使用阻塞队列来缓冲任务
new ThreadPoolExecutor.CallerRunsPolicy() // 如果队列已满,将在调用者线程中执行
);
/**
* <h2>获取csv解析器</h2>
* 通过路径或者文件还有流获取csv管理器,便于操作csv文件
*
* @param obj 可以是csv文件路径或者读取的文件或者流
* @param format (可空) CSV文件读取格式化器,为空使用默认构造
* @return {@link CSVParser} csv文件管理器
* @throws IOException
*/
public static CSVParser getCSVParser(Object obj, @Nullable CSVFormat format) throws IOException {
BufferedReader br = null;
CSVFormat csvFormat;
//if(obj instanceof String str)
if (obj instanceof String) {
String csvPath = (String) obj;
br = new BufferedReader(new FileReader(csvPath));
} else if (obj instanceof File) {
br = new BufferedReader(new FileReader((File) obj));
} else if (obj instanceof Reader) {
br = new BufferedReader((Reader) obj);
}
//创建csv文件读取格式
if (null == format) {
csvFormat = CSVFormat.EXCEL
.builder()
//.setNullString("") //将空字符串转为null,方便跳过
.setTrim(true)
.setHeader() //读取表头(用于后面isMapping的判断)
.setSkipHeaderRecord(true) //跳过表头
.setIgnoreEmptyLines(true) //跳过空行
.get();
} else csvFormat = format;
return CSVParser.parse(br, csvFormat);
}
/**
* <h2>通过csv路径读取文件</h2>
* <p>
* 读取csv文件获取每行的数据,只能读取小文件或者简易操作!
*
* @param csvPath csv文件路径
* @param readLineNum 读取行数(必填)
* @return List<String>
* @throws IOException
*/
public static List<String[]> read(String csvPath, @NotNull Long readLineNum) {
List<String[]> rowData = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new FileReader(csvPath))) {
List<String> collect = br.lines().limit(readLineNum).collect(Collectors.toList());
for (String line : collect) {
rowData.add(line.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)", -1));
}
} catch (FileNotFoundException e) {
log.error(e.getMessage());
throw new RuntimeException("csv文件读取失败或文件路径不存在");
} catch (IOException e) {
log.error("文件读取异常");
throw new RuntimeException(e);
}
return rowData;
}
/**
* <h2>多线程处理csv文件</h2>
*
* @param obj csv文件路径或者{@link File}对象
* @param func 自定义csv文件数据处理函数
* @throws IOException
*/
public static void read(Object obj, @NotNull Function<ConcurrentLinkedQueue<CSVRecord>, ?> func) throws IOException {
CSVParser csvParser = getCSVParser(obj, null);
//获取迭代器读,是实时去读取而不是全部读取
Iterator<CSVRecord> iterator = csvParser.iterator();
//保证数据同步
ConcurrentLinkedQueue<CSVRecord> queue = new ConcurrentLinkedQueue<>();
for (int i = 0; i < READ_SIZE_ && iterator.hasNext(); i++) {
queue.add(iterator.next());
//读取到达最大值则进行处理
if (i == READ_SIZE_ - 1 && iterator.hasNext()) {
i = -1;
final ConcurrentLinkedQueue<CSVRecord> tempQueue = queue;
queue = new ConcurrentLinkedQueue<>();
executor.execute(() -> func.apply(tempQueue));
//executor.submit(() -> func.apply(tempQueue));
}
}
//处理剩余数据
if (!queue.isEmpty()) {
func.apply(queue);
}
}
/**
* <h2>获取csv文件类型</h2>
* 通过文件名判断杂散电流系列文件的类型
*
* @param fileName csv文件名
* @return String
*/
public static String getType(@NotNull String fileName) {
String typeName = fileName.split("\\.|#")[1];
return typeName;
}
/**
* <h2>csv文件输出</h2>
* 生成csv文件
*
* @param csvPath 输出路径
* @param data 输出数据
* @throws IOException
*/
public static void write(String csvPath, List<String[]> data) throws IOException {
try (FileWriter writer = new FileWriter(csvPath)) {
for (String[] row : data) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < row.length; i++) {
String field = row[i];
//字符串处理
if (field.contains(",") || field.contains("\"")) {
field = field.replace("\"", "\"\"");
sb.append("\"").append(field).append("\"");
} else {
sb.append(field);
}
if (i < row.length - 1) sb.append(",");
}
sb.append("\n");
writer.write(sb.toString());
}
}
}
}
自定义数据读取那块,我自己写了一个接口,让需要使用csv文件的类去实现并且自定义处理数据,在类中定义了线程安全的原子类和同步队列,用于存储读取的数据方便后续的计算.
//通电位最大最小值 E列
private final AtomicDouble onMaxValue = new AtomicDouble(Double.NEGATIVE_INFINITY);
private final AtomicDouble onMinValue = new AtomicDouble(-Double.MIN_VALUE);
//通电位平均值
private final AtomicDouble onSum = new AtomicDouble(0);
private final AtomicInteger onCount = new AtomicInteger(0);
//G列数据
private final ConcurrentLinkedQueue<Double> gValues = new ConcurrentLinkedQueue<>();
@Override
public boolean process(ConcurrentLinkedQueue<CSVRecord> queue) {
for (CSVRecord csvRecord : queue) {
//尝试获取数据
String dcOnStr = csvRecord.get(P_DC_READ_);
String dcOffStr = csvRecord.get(P_DC_INST_OFF_);
//String dcOnStr = csvRecord.get("A"); 这里get是通过标头获取字段值也就是每列的名称,当然也可以通过下标获取,但是很蠢
//判断数据
if (dcOnStr.isEmpty() && dcOffStr.isEmpty()) continue;
else if (!dcOnStr.isEmpty()) {
double valDouble = EidpsTools.setCompareAtomicDouble(dcOnStr, onMaxValue, onMinValue);
onSum.addAndGet(valDouble);
onCount.incrementAndGet();
} else {
double valDouble = EidpsTools.setCompareAtomicDouble(dcOffStr, offMaxValue, offMinValue);
offSum.addAndGet(valDouble);
offCount.incrementAndGet();
gValues.add(valDouble);
}
}
return true;
}
@Override
public Object handler(Object obj) {
//beforeProcess();
try {
CsvTools.read(obj, this::process);
} catch (IOException e) {
throw new RuntimeException(e);
}
afterProcess();
return zlzsExcportTemp;
}
可以根据自己需要去自定义方法,然后统一的放到工具类中执行并处理,我这里还可以优化但是没有进一步优化了,到此为止。