27

我有 100000 行的大 txt 文件。我需要启动 n 个线程并从这个文件中为每个线程提供唯一的行。

做这个的最好方式是什么?我想我需要逐行读取文件,并且迭代器必须是全局的才能锁定它。将文本文件加载到列表中会很耗时,而且我会收到OutofMemory异常。有任何想法吗?

4

6 回答 6

41

您可以使用File.ReadLines 方法逐行读取文件,而无需一次将整个文件加载到内存中,并使用Parallel.ForEach 方法在多个线程中并行处理行:

Parallel.ForEach(File.ReadLines("file.txt"), (line, _, lineNumber) =>
{
    // your code here
});
于 2013-06-19T10:05:58.877 回答
23

在执行我自己的基准测试以将 61,277,203 行加载到内存中并将值推入 Dictionary / ConcurrentDictionary() 之后,结果似乎支持@dtb 上面的回答,即使用以下方法是最快的:

Parallel.ForEach(File.ReadLines(catalogPath), line =>
{

}); 

我的测试还显示了以下内容:

  1. File.ReadAllLines() 和 File.ReadAllLines().AsParallel() 似乎在这种大小的文件上以几乎完全相同的速度运行。查看我的 CPU 活动,它们似乎都使用了我的 8 个内核中的两个?
  2. 首先使用 File.ReadAllLines() 读取所有数据似乎比在 Parallel.ForEach() 循环中使用 File.ReadLines() 慢得多。
  3. 我还尝试了生产者/消费者或 MapReduce 样式模式,其中一个线程用于读取数据,而第二个线程用于处理数据。这似乎也没有优于上面的简单模式。

我已包含此模式的示例以供参考,因为它不包含在此页面中:

var inputLines = new BlockingCollection<string>();
ConcurrentDictionary<int, int> catalog = new ConcurrentDictionary<int, int>();

var readLines = Task.Factory.StartNew(() =>
{
    foreach (var line in File.ReadLines(catalogPath)) 
        inputLines.Add(line);

        inputLines.CompleteAdding(); 
});

var processLines = Task.Factory.StartNew(() =>
{
    Parallel.ForEach(inputLines.GetConsumingEnumerable(), line =>
    {
        string[] lineFields = line.Split('\t');
        int genomicId = int.Parse(lineFields[3]);
        int taxId = int.Parse(lineFields[0]);
        catalog.TryAdd(genomicId, taxId);   
    });
});

Task.WaitAll(readLines, processLines);

这是我的基准:

在此处输入图像描述

我怀疑在某些处理条件下,生产者/消费者模式可能优于简单的 Parallel.ForEach(File.ReadLines()) 模式。但是,它没有在这种情况下。

于 2014-10-10T05:44:06.330 回答
7

在一个线程上读取文件,将其行添加到阻塞队列中。开始N从该队列读取任务。设置队列的最大大小以防止内存不足错误。

于 2013-06-19T10:02:38.803 回答
5

就像是:

public class ParallelReadExample
{
    public static IEnumerable LineGenerator(StreamReader sr)
    {
        while ((line = sr.ReadLine()) != null)
        {
            yield return line;
        }
    }

    static void Main()
    {
        // Display powers of 2 up to the exponent 8:
        StreamReader sr = new StreamReader("yourfile.txt")

        Parallel.ForEach(LineGenerator(sr), currentLine =>
            {
                // Do your thing with currentLine here...
            } //close lambda expression
        );

        sr.Close();
    }
}

认为它会工作。(这里没有 C# 编译器/IDE)

于 2013-06-19T10:14:47.343 回答
4

如果要将线程数限制为n,最简单的方法是使用AsParallel()withWithDegreeOfParallelism(n)来限制线程数:

string filename = "C:\\TEST\\TEST.DATA";
int n = 5;

foreach (var line in File.ReadLines(filename).AsParallel().WithDegreeOfParallelism(n))
{
    // Process line.
}
于 2013-06-19T10:31:44.910 回答
3

As @dtb mentioned above, the fastest way to read a file and then process the individual lines in a file is to: 1) do a File.ReadAllLines() into an array 2) Use a Parallel.For loop to iterate over the array.

You can read more performance benchmarks here.

The basic gist of the code you would have to write is:

string[] AllLines = File.ReadAllLines(fileName);
Parallel.For(0, AllLines.Length, x =>
{
    DoStuff(AllLines[x]);
    //whatever you need to do
});

With the introduction of bigger array sizes in .Net4, as long as you have plenty of memory, this shouldn't be an issue.

于 2014-10-06T06:39:57.323 回答