首页 > 解决方案 > 如何使用缓冲区和多线程在大型文本文件中搜索字符串

问题描述

我想在一个非常大的文本文件(包含数十 GB 的文本)中找到一个字符串。我需要使用缓冲区和多线程,这就是我想做的:

  1. 在每次迭代时使用缓冲区创建一个循环读取文本块。
  2. 将块拆分为给定数量的线程。
  3. 每个线程将在它收到的文本部分中搜索字符串。
  4. 如果找到字符串,则打印其位置,否则读取文本文件的另一块。

这就是我试图做的:

string[] lines = System.IO.File.ReadAllLines(@textfile);
            int counter = lines.Length / nThreads;
            int start = 0;
            int end = counter;
            (int,int)[] results = new (int, int)[nThreads];

            results.ToList().ForEach(i => Console.WriteLine(i.ToString()));
            for (int i = 0; i < nThreads; i++)
            {
                Thread thread1 = new Thread(() => { results[i] = ThreadSearch.SubarraySearch(StringToSearch, Delta, lines, start, end); });
                // ThreadSearch - function that search the string in the array
                thread1.Start();
                thread1.Join();
            }
// at the end i will go through the results array see if any of the  threads found something
   

}

现在我在实现这个算法时有两个问题:

  1. 我现在不知道如何同时运行所有线程并在找到结果时停止。
  2. 我只知道如何使用缓冲区而不是文本中的块来逐行读取。

输入数据的约束和不变量:

  1. 我不知道文本文件的确切大小,只是它太大而无法一次读取,但我知道线程的最大缓冲区大小为 10k。
  2. 我正在搜索的字符串的大小是未知的——可能是文本文件中的一个单词,它只包含字母和数字,没有空格或换行符。

我是 C# 的新手,所以任何帮助都会很棒。

标签: c#multithreadingsearch

解决方案


这是一种PLINQ方法。通过使用 PLINQ 库,您可以避免显式线程管理。该库使用线程为您管理ThreadPool线程。AsParallel运算符表示后续运算符将并行执行。

string filePath = @"C:\YourFile.txt";
string searchedText = "TheTextToFind";

bool fileContainsText = File
    .ReadLines(filePath)
    .AsParallel()
    .WithDegreeOfParallelism(Environment.ProcessorCount)
    .Any(line => line.Contains(searchedText, StringComparison.Ordinal));

此解决方案使用该方法,因此它在不包含 CR 或 LF 字符File.ReadLines的假设下工作。searchedText否则不会检测到字符串。

上述解决方案不太可能比普通的 LINQ 查询更快,因为与将每一行传递到不同线程相关的同步开销。一种更复杂的方法是分批处理生产线,例如每批 1000 条生产线。目前没有用于批处理枚举的可用内置运算符,但您可以使用System.Interactive包中的Buffer运算符:

bool fileContainsText = Partitioner
    .Create(File.ReadLines(filePath).Buffer(1000),
        EnumerablePartitionerOptions.NoBuffering)
    .AsParallel()
    .WithDegreeOfParallelism(Environment.ProcessorCount)
    .Any(array => array.Any(line =>
        line.Contains(searchedText, StringComparison.Ordinal)));

您还可以使用MoreLinq包中的Batch运算符。

EnumerablePartitionerOptions.NoBuffering配置通过指示 PLINQ 避免缓冲输入可枚举的元素,而是立即处理每个新接收到的元素,从而优化了内存使用。这在处理批处理时很重要,因为每个批处理在内存方面可能非常大,并且尽快回收此内存比最小化同步开销更重要(这是缓冲查询输入的意图,默认行为)。


推荐阅读