Java利用多线程和分块实现快速读取文件

 更新时间:2023年09月08日 09:12:18   作者:L7ink  
在工作中经常会有接收文件并且读取落库的需求,读取方式都是串行读取,所以本文主要为大家介绍一下如何利用多线程和分块实现快速读取文件,希望对大家有所帮助

背景

在工作中经常会有接收文件并且读取落库的需求,读取方式都是串行读取,即一行行的读取,如果文件小还可以,但是如果文件比较大,类似于全量文件的话,这样的读取就会非常效率低。

本文主要介绍的是如何正确的将文件分块,多线程的实现方式有多种,这里用的是CompletableFuture

方法

因为我们文件里面的每条数据之间没有任何依赖关系也不存在顺序要求。如何提高读取速度,第一个想到当然就是并行读取文件,并行读取的前提就是要给文件分块,让每个线程只读取对应分块的数据,先看看我们的文件格式

可以看见我们的文件格式每一行的长度不一,同时文件也无法像TCP通过指定数据体的长度来读取数据,所以如何能正确的分块是整个方法的关键,如果分多或者分少了就会导致数据读取错误的可能。

可以看见错误的分块就会导致我们读取的数据会被截取掉一部分,截取掉多少都是随机的。这里我们用的方法是用填充来让每个分块都是正确的。具体来说就是我们在分块的时候,判断一下当前的分块位置会不会导致数据被截取,因为我们的数据是一行行的,所以最好的分块位置都是分在了行尾。如果说当前的分块位置是在一行的中间的话,那我们就要移动这个分块的位置到这行的行尾去

private static int THREAD_NUM = 5;
long total = file.length();
long chunkSize = total < THREAD_NUM ? total : total / THREAD_NUM;

先确定要用几个线程并行读取,然后根据线程数和文件的大小来确定每一块的大小,接下来就进行判断是否需要填充

    private static long padding(long start, long chunkSize, File file) {
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")){
            randomAccessFile.seek(start + chunkSize);
            boolean eol = false;
            //判断当前的位置是否需要填充,如果当前没有数据或者是行尾,则不需要填充
            switch (randomAccessFile.read()) {
                case -1:
                case '\n':
                    eol = true;
                    break;
                case '\r':
                    eol = true;
                    break;
                default:
                    break;
            }
            //如果符合填充条件,对其进行填充,首先是读取一行数据,然后计算出这行数据的长度,然后将这行数据的长度加上前面读取了一字节,然后将这些长度加到chunkSize上
            if (!eol){
                String readLine = randomAccessFile.readLine();
                chunkSize += readLine.getBytes().length;
                //加上前面读取的一字节
                chunkSize += 1;
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return chunkSize;
    }

如何填充的可以看看代码注释,是参照了readline的实现思路。 经过填充后就可以确保每块的分块的最后一个位置都是在行尾。将每个分块的起始位置和分块大小储存起来再结合上CompletableFuture就可以多线程分块读取文件了

        Map<Long, Long> chunkMap = new HashMap<>();
        for (int i = 0; i < THREAD_NUM; i++) {
            chunkSize = padding(start, chunkSize, file);
            chunkMap.put(start, chunkSize);
            start += chunkSize;
        }
        CompletableFuture.allOf(chunkMap.entrySet().stream().map(entry -> CompletableFuture.runAsync( () -> handlerReportTreeBaseData(entry.getKey(), entry.getValue()))).toArray(CompletableFuture[]::new))
                .exceptionally(throwable -> {
                    System.out.println(throwable.getMessage());
                    return null;
                }).join();

完整实现

接下来给出整个的实现代码,欢迎大家看看有没有什么我没有考虑到的,有可能的隐藏BUG和还能优化改善的地方,欢迎讨论

public class SpiltFIle {
    private static int THREAD_NUM = 5;
    private static void splitChunks() {
        File file = new File("test.txt");
        long total = file.length();
        long chunkSize = total < THREAD_NUM ? total : total / THREAD_NUM;
        long start = 0;
        Map<Long, Long> chunkMap = new HashMap<>();
        for (int i = 0; i < THREAD_NUM; i++) {
            chunkSize = padding(start, chunkSize, file);
            handlerReportTreeBaseData(start, chunkSize);
            chunkMap.put(start, chunkSize);
            start += chunkSize;
        }
        CompletableFuture.allOf(chunkMap.entrySet().stream().map(entry -> CompletableFuture.runAsync( () -> handlerReportTreeBaseData(entry.getKey(), entry.getValue()))).toArray(CompletableFuture[]::new))
                .exceptionally(throwable -> {
                    System.out.println(throwable.getMessage());
                    return null;
                }).join();
    }
    private static long padding(long start, long chunkSize, File file) {
        try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")){
            randomAccessFile.seek(start + chunkSize);
            boolean eol = false;
            //判断当前的位置是否需要填充,如果当前没有数据或者是行尾,则不需要填充
            switch (randomAccessFile.read()) {
                case -1:
                case '\n':
                    eol = true;
                    break;
                case '\r':
                    eol = true;
                    break;
                default:
                    break;
            }
            //如果符合填充条件,对其进行填充,首先是读取一行数据,然后计算出这行数据的长度,然后将这行数据的长度加上前面读取了一字节,然后将这些长度加到chunkSize上
            if (!eol){
                String readLine = randomAccessFile.readLine();
                chunkSize += readLine.getBytes().length;
                chunkSize += 1; //加上前面读取的一字节
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return chunkSize;
    }
    private static void handlerReportTreeBaseData(long start, long chunkSize) {
        try (RandomAccessFile randomAccessFile = new RandomAccessFile("test.txt", "r")) {
            randomAccessFile.seek(start);
            long currentCount = 0L;
            String line;
            while (currentCount < chunkSize && (line = randomAccessFile.readLine()) != null){
                if (!line.isEmpty()){
                    currentCount += line.getBytes().length + System.lineSeparator().getBytes().length;
                    System.out.println(line);
                }
            }
        }catch (Exception ignored){
        }
    }
    public static void main(String[] args) throws IOException {
        SpiltFIle.splitChunks();
    }
}

最后也是能正常的读取完文件

以上就是Java利用多线程和分块实现快速读取文件的详细内容,更多关于Java读取文件的资料请关注脚本之家其它相关文章!

相关文章

  • String实例化及static final修饰符实现方法解析

    String实例化及static final修饰符实现方法解析

    这篇文章主要介绍了String实例化及static final修饰符实现方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • logback输出日志屏蔽quartz的debug等级日志方式

    logback输出日志屏蔽quartz的debug等级日志方式

    这篇文章主要介绍了logback输出日志屏蔽quartz的debug等级日志方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • Java流程控制之顺序结构

    Java流程控制之顺序结构

    Java中的流程控制语句可以这样分类:顺序结构,选择结构,循环结构。下面文章我们就来看看来顺序结构的详细介绍,主要以顺序结构简单图示及详细解说该内容,需要的小伙伴可以参考一下
    2021-12-12
  • 详解Java线性结构中的链表

    详解Java线性结构中的链表

    除了一些算法之外,我们还有掌握一些常见的数据结构,比如数组、链表、栈、队列、树等结构,所以接下来就给大家详细讲解一下线性结构中的链表,需要的朋友可以参考下
    2023-07-07
  • SpringBoot3整合Mybatis完整版实例

    SpringBoot3整合Mybatis完整版实例

    本文详细介绍了SpringBoot3整合MyBatis的完整步骤,包括添加数据库驱动和MyBatis依赖、配置数据源和MyBatis、创建表和Bean类、编写Mapper接口和XML文件、创建Controller类以及配置扫描包,通过这些步骤,可以实现SpringBoot3与MyBatis的成功整合,并进行功能测试
    2025-01-01
  • 一个Java的main方法在JVM中的执行流程示例详解

    一个Java的main方法在JVM中的执行流程示例详解

    main方法是Java程序的入口点,程序从这里开始执行,这篇文章主要介绍了一个Java的main方法在JVM中执行流程的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-09-09
  • 为什么说HashMap线程不安全

    为什么说HashMap线程不安全

    本文主要介绍了为什么说HashMap线程不安全,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • 详解spring cloud构建微服务架构的网关(API GateWay)

    详解spring cloud构建微服务架构的网关(API GateWay)

    这篇文章主要介绍了详解spring cloud构建微服务架构的网关(API GateWay),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-01-01
  • SpringBoot中GlobalExceptionHandler异常处理机制详细说明

    SpringBoot中GlobalExceptionHandler异常处理机制详细说明

    Spring Boot的GlobalExceptionHandler是一个全局异常处理器,用于捕获和处理应用程序中发生的所有异常,这篇文章主要给大家介绍了关于Java中GlobalExceptionHandler异常处理机制的相关资料,需要的朋友可以参考下
    2024-03-03
  • JavaBean实体类处理外键过程解析

    JavaBean实体类处理外键过程解析

    这篇文章主要介绍了JavaBean实体类处理外键过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07

最新评论