使用多线程将文本文件拆分为java中的块

时间:2016-06-23 11:18:44

标签: java multithreading split cpu-usage

我根据公式(文件总大小/分割大小)拆分了一个文本文件(50GB)。现在顺序完成拆分是单线程完成,如何更改此代码以执行多线程中的拆分(即并行线程应该分割文件并存储在文件夹中)我不想读取文件,因为它将利用更多的CPU。我的主要目标是我必须减少CPU利用率并以更少的时间快速完成文件的拆分。我有8个cpu核心。

有什么建议吗?提前谢谢。

public class ExecMap {


public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {

    String FilePath = "/home/xm/Downloads/wikipedia_50GB/wikipedia_50GB/file21";
    File file = new File(FilePath);
    long splitFileSize = 64 * 1024 * 1024;
    long fileSize = file.length();
    System.out.println(+fileSize);
    int mappers = (int) (fileSize / splitFileSize);
    System.out.println(+mappers);
    ExecMap exec= new ExecMap();
    exec.mapSplit(FilePath,splitFileSize,mappers,fileSize);
}

private static void mapSplit(String FilePath, long splitlen, int mappers,long fileSize) {
ExecutorService executor = Executors.newFixedThreadPool(1);
        executor.submit(() -> {
            long len = fileSize;
            long leninfile = 0, leng = 0;
            int count = 1, data;
            try {
                long startTime = System.currentTimeMillis(); // Get the start Time
                long endTime = 0;
                System.out.println(startTime);
                File filename = new File(FilePath);
                InputStream infile = new BufferedInputStream(new FileInputStream(filename));
                data = infile.read();
                while (data != -1) {

                    String name = Thread.currentThread().getName();
                    System.out.println("task started: " + name +" ====Time " +System.currentTimeMillis());
                    filename = new File("/home/xm/Desktop/split/" +"Mapper " + count + ".txt");
                    OutputStream outfile = new BufferedOutputStream(new FileOutputStream(filename));
                    while (data != -1 && leng < splitlen) {
                        outfile.write(data);
                        leng++;
                        data = infile.read();
                    }
                    leninfile += leng;
                    leng = 0;
                    outfile.close();
                    count++;
                    System.out.println("task finished: " + name);
                }
                endTime = System.currentTimeMillis();
                System.out.println(endTime);
                long msec = endTime - startTime;
                long sec = endTime - startTime;
                System.out.println("Difference in milli seconds: " + msec); //Print the difference in mili seconds
                System.out.println("Differencce in Seconds: " + sec / 1000);


            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            executor.shutdownNow();
        });


}
}

3 个答案:

答案 0 :(得分:1)

您可以使用RandomAccessFile并使用seek跳到某个位置。

这样你就可以给你的执行者一个起始位置和一个结束位置,这样每个执行者都可以处理文件的一小部分

但正如提到的,你的问题将是磁盘I / O

答案 1 :(得分:0)

基本的多线程方法是接受一项任务,将其划分为子任务,这些子任务可以作为单独的工作单元完成,并为每个子任务创建一个线程。当线程可以彼此独立且不需要任何类型的通信且不共享任何资源时,这种方法效果最佳。

房屋建筑作为一个类比

因此,如果我们正在建造房屋,则需要按特定顺序完成一些子任务。在建造房屋之前必须有一个基础。在屋顶可以穿上之前,墙壁必须就位。

然而,一些子任务可以独立完成。当管道工安装管道时,屋顶可能会被瓦楞,而且砖层正在房屋的外面筑砖。

关于要解决的问题的基本想法

在文件拆分的情况下,基本方法是执行任务,拆分文件,并将其划分为多个子任务,分配要分割到每个线程的文件的一部分。

然而,这个特殊的任务,分割文件,有一个共同的工作,将创建一个瓶颈,并可能需要线程之间的某种同步,当他们从原始文件中读取要分割。让多个线程访问同一个文件需要以线程可以访问其文件的已分配部分的方式完成文件访问。

好消息是,由于唯一共享的是原始文件并且只是从中读取,因此您无需担心在Java级别同步文件读取。

第一种方法

我首先考虑的方法是将输出文件的数量除以线程数。然后,每个线程将使用自己的文件读取器打开文件,以便每个线程独立于其文件I / O的其他线程。这种方式虽然原始文件是共享的,但每个线程都有自己的关于文件读取位置的数据,因此每个线程都是从文件中独立读取的。

然后,每个线程将创建自己的一组输出文件,并从原始文件中读取并写入输出文件。线程将一次创建一个输出文件,从原始文件中的指定偏移开始,从原始文件读取并写入输出文件。

这样做可以保持每个线程的工作独立性。每个线程都有自己的原始文件访问数据。每个线程都有自己指定的原始文件区域。每个线程都会生成自己的一组输出文件。

其他注意事项

在操作系统级别共享文件系统。因此操作系统将需要交错和多路复用文件系统访问。对于像这样的应用程序,从磁盘文件中读取数据然后立即写回另一个磁盘文件,大多数情况下应用程序正在等待操作系统执行所请求的I / O操作。

对于磁盘文件,需要执行几个较低级别的操作,例如:(1)在磁盘上查找文件的位置,(2)寻找该位置,以及(3)读取或写入请求的数据量。操作系统为应用程序执行所有这些操作,而操作系统正在执行这些操作,应用程序等待。

因此,在您的多线程应用程序中,每个线程都会向操作系统询问磁盘I / O,因此每个线程将花费大部分时间等待操作系统完成磁盘I / O请求,磁盘I / O正在从原始文件读取或写入新的拆分文件。

由于此磁盘I / O可能是需要最多时间的边界操作,因此问题是是否可以减少所花费的时间。

第二种方法

所以另一种架构就是拥有一个只能从原始文件中读取并读入大块的单个线程,这些块的大小是分割文件大小的几倍。然后使用一个或多个其他线程来获取每个块并创建输出拆分文件。

从原始文件读取的单个线程读取分割文件块并将该块提供给另一个线程。然后另一个线程创建拆分文件并将块写出。当另一个线程正在执行该子任务时,单个线程从原始文件读取下一个块,并使用第二个线程将该块写入拆分文件。

这种方法应该允许磁盘I / O更高效,因为原始文件的大块正被读入内存。原始大文件的磁盘I / O按顺序完成,允许操作系统更有效地执行磁盘搜索和磁盘I / O.

在第一种方法中,随机访问原始文件,这要求在每个线程发出磁盘读取请求时,必须更频繁地重新定位从磁盘读取数据的磁头。

最后的想法:通过衡量

来测试预测

为了确定哪种方法实际上更有效,需要同时尝试这两种方法。虽然您可以根据操作系统和磁盘硬件的工作原理进行预测,但在实际尝试并测量这两种方法之前,您不会知道一个方法是否优于另一个方法。

最后,最有效的方法可能是只有一个线程读取原始文件的大块,然后写出较小的分割文件。

多线程可能带来的好处

另一方面,如果您有多个线程被分割文件的大块,则可以使用多个线程更有效地调度创建,打开和关闭文件所涉及的一些操作系统开销。使用多个线程可以允许操作系统的文件管理子系统和磁盘I / O例程通过在多个挂起的磁盘I / O请求中进行选择来更有效地调度磁盘I / O.

由于创建和销毁线程的开销,您可能希望在应用程序启动时创建一组工作线程,然后将特定的拆分文件I / O任务分配给线程。当线程完成该分配时,它将等待另一个。

答案 2 :(得分:0)

您不会看到启动多个线程(如原始问题的评论中的许多人所指出的)和#34;并行拆分文件的任何优势&#34;。

让多个线程并行处理大型任务的某些部分,只有当它们彼此独立行动时才能加快 。因为,在这种情况下,耗时的部分是读取50 Gb的文件并将其写为较小的文件,这不是由Java完成的,而是由OS完成的(最终,由磁盘驱动程序必须读取并稍后写入所有这些字节),拥有多个线程只会增加一小部分开销(用于线程创建和调度),使一切变慢。

此外,旋转磁盘中的顺序读取(SSD不受此规则限制)比随机读取和写入快得多 - 如果许多线程正在从磁盘的不同部分读取和写入,吞吐量将比一个线程可以完成所有事情。

以这种方式思考 - 你有一个卡车司机(操作系统+磁盘),并且必须将地方A处的一大堆砖块分成C,D和E处的较小堆盘;和砖只能用卡车旅行。只有卡车司机和你,主管发出命令。是否会聘请更多的主管(线程)来并行处理订单?不 - 你只会妨碍对方和卡车司机,试图取悦所有人,需要更多的旅程来驾驶少量的砖来做同样的工作。