【问题标题】:Reading several txt files searching for a String with multithreading使用多线程读取多个 txt 文件搜索字符串
【发布时间】:2017-05-30 16:44:56
【问题描述】:

我有一个包含 100 个 .txt 文件的文件夹,每个文件 20 或 + MB。 所有文件都有大约2*10^5 lines of UTF-8 encoded text

什么是可能使用多线程来查找哪些文件包含固定密钥字符串的最快方法? (包含的条件与 java .contains() 函数相同,即普通子字符串)。

我在 SO 上找到了几种方法,但没有使用 multithreading为什么?),而且它们似乎都根据要求改变速度,我似乎无法理解哪个的方法对我来说更好。

例如这个超级复杂的方法:

https://codereview.stackexchange.com/questions/44021/fast-way-of-searching-for-a-string-in-a-text-file

似乎比使用BufferedReader.contains() 函数的简单逐行搜索慢2 倍。怎么可能?

如何才能充分发挥多线程的潜力?该程序在非常强大的多核机器上运行。

我要查找的输出是哪些文件包含该字符串,以及可能在哪一行。

【问题讨论】:

  • 多线程在最好的情况下是复杂的,在大多数情况下,为人们提供单线程解决方案更简单,这样他们就可以理解问题的解决方案,而不是增加可能进一步混淆他们的复杂性,这就是“为什么”。多线程也并不总能带来(显着)性能提升,因此您需要进行一些测试以了解优势在哪里。我的一般建议是从ExecutorService
  • 甚至可能是ThreadPoolExecutor,这样您就可以更好地控制活动线程的数量。这样,您可以将所有想要完成的任务转储到执行程序中并等待结果(以多种方式之一)
  • 根据您的硬件,文件扫描不会受益于多线程,因为性能瓶颈是硬盘。但正如我所说,这取决于 CPU 与磁盘的性能。也许 2 或 3 个线程会扫描得更快,但 100 个线程可能会完全降低性能。
  • 如果文件是静态的并且您打算多次搜索它们,那么为什么不为它们创建索引。这将比每次从头开始扫描它们要快得多。如果它们是动态的,那么只需根据文件更改事件相应地更新索引。这就是我想做的事情。这就是数据库如此出色的原因,它们可以索引所有内容并具有查询语言。
  • 我正在考虑将文件分成 8 个组,并在不同的线程上解析每个组(我只是在这里假设,但我想这是最佳数字,因为 cpu 的核心(它是英特尔 i7))。这些文件都在同一个磁盘上,这是一个高端、非常快的 SSD。不过我可能完全不在了。我不知道磁盘如何处理多线程。

标签: java string multithreading parsing


【解决方案1】:

下面的代码完成了这项工作。

它将进入您的目录并查找所有文件。 然后将为每个文件创建一个新线程并查找目标字符串。

确保根据需要更改TheThread类中文件夹和目标字符串的路径

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;


//class used for thread
class TheThread implements Runnable {

    int counter = 0;

    //to get stream of paths
    Stream<Path> streamOfFiles = Files.walk(Paths.get("./src/Multi_tasking/Files"));

    //List of total all files in the folder
    List<Path> listOfFiles = streamOfFiles.collect(Collectors.toList());

    //because Files.walk may throw IOException
    public TheThread() throws IOException {
    }


    @Override
    public void run() {

        //increments counter to access the indexes of the list
        counter++;

        //Calling the method for search file at index counter and  target String
        SearchTextInMultipleFilesUsingMultiThreading.lookIn(listOfFiles.get(counter), "target String");
    }
}

public class SearchTextInMultipleFilesUsingMultiThreading {

    //method responsible for searching the target String in file
    public static void lookIn(Path path, String text) {
        try {
            List<String> texts = Files.readAllLines(path);
            boolean flag = false;
            for (int i = 0; i < texts.size(); i++) {
                String str = texts.get(i);
                if (str.contains(text)) {
                    System.out.println("Found \"" + text + "\" in " + path.getFileName() + " at line : " + (i + 1) + " from thread : " + Thread.currentThread().getName());
                    flag = true;
                }
            }
            if (!flag) {
                System.out.println("\"" + text + "\" not found in " + path.getFileName() + " through thread : " + Thread.currentThread().getName());
            }

        } catch (IOException e) {
            System.out.println("Error while reading " + path.getFileName());
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {

        //creating object of our thread class
        TheThread theThread = new TheThread();

        //getting the number of files in the folder
        int numberOfFiles = theThread.listOfFiles.size() - 1;

        //if the folder doesn't contain any file at all
        if (numberOfFiles == 0) {
            System.out.println("No file found in the folder");
            System.exit(0);
        }

        //creating the List to store threads
        List<Thread> listOfThreads = new ArrayList<>();

        //keeping required number of threads inside the list
        for (int i = 0; i < numberOfFiles; i++) {
            listOfThreads.add(new Thread(theThread));
        }

        //starting all the threads
        for (Thread thread :
                listOfThreads) {
            thread.start();

        }
    }

}

【讨论】:

    【解决方案2】:

    我会让其他问题的答案不言自明,但多线程不太可能对数据存储在单个磁盘上的 I/O 绑定任务有所帮助。假设您的文件夹存储在单个磁盘上,磁盘缓存最优化的用例是单线程访问,因此这可能是最有效的解决方案。原因是从磁盘读取数据可能比在数据加载到内存后查看数据要慢,因此磁盘读取是速率限制的。

    使用 BufferedReader 和 contains() 函数的简单解决方案可能是最快的,因为这是可能高度优化的库代码。

    现在,如果您的数据被分片到多个磁盘上,则可能值得运行多个线程,具体取决于操作系统如何进行磁盘缓存。如果您要对不同的字符串进行多次搜索,在第一次搜索时并非全部都知道,因此单遍方法不起作用,那么将所有文件加载到内存中然后只进行多线程搜索可能是值得的在内存上。但是,您的问题不再是真正的文件搜索问题,而是更一般的数据搜索问题。

    【讨论】:

    • 如何以某种方式将所有文件加载到内存中,然后从那里使用多线程进行扫描?可用 RAM 量不是问题。
    • @LoryA 如果您只想进行一次搜索,那么该方法仍然需要从磁盘读取所有数据一次;你无法解决这个问题。如果您要进行多次搜索,那么这种方法可能是值得的。
    • 100 个文件 x 20 MB/文件 = 大约 2 GB RAM 仅用于保存内容、程序需要的内容、Java 需要的内容以及运行的其他应用程序需要的内容,包括操作系统.划分为分片进行处理可能会出现缓存争用,线程会破坏引用的局部性,以及其他线程争用问题。即使使用快速存储硬件,I/O 实际上也可以让 CPU 线程有时间工作。一个线程获取,而另一个进程。如果您将读取设置为一个或两个生产者线程,并作为消费者进行处理,这可能会加快您的速度。
    • @Warren Dew,从磁盘读取一次,好的,但我目前的方法的行间读取涉及使用 .contains() 进行扫描。所以我在读取之间造成了延迟。如果我首先从磁盘读取所有内容,然后使用多线程扫描 (.contains()),我会做一些有意义的事情吗?在这种情况下,将文件分成 8 组以充分利用内核是否有意义?
    • @Lew Bloch 我不太了解你所说的,我将添加一些可能会有所帮助的信息:该机器具有 32GB 的 DDR4 内存。在执行此程序时,系统的其余部分使用了大约 4 GB。该应用程序只是一个执行此解析然后退出的超级基本 GUI。文件数量可以从 40 到最多 100 个不等。文件大小可以从 20 到 22 MB 不等。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-28
    • 2015-07-12
    • 2014-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多