处理CSV大文件

新的项目需要处理其他设备生成的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&lt;String&gt;
     * @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;
    }

 可以根据自己需要去自定义方法,然后统一的放到工具类中执行并处理,我这里还可以优化但是没有进一步优化了,到此为止。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值