【问题标题】:Reading a file line by line in C#在 C# 中逐行读取文件
【发布时间】:2010-11-19 06:20:55
【问题描述】:

我正在尝试读取一些文本文件,其中每一行都需要处理。目前我只是使用 StreamReader,然后单独读取每一行。

我想知道是否有更有效的方法(在 LoC 和可读性方面)使用 LINQ 来做到这一点,而不会影响操作效率。我看到的示例涉及将整个文件加载到内存中,然后对其进行处理。但是,在这种情况下,我认为这不会非常有效。在第一个示例中,文件可以达到大约 50k,而在第二个示例中,并非文件的所有行都需要读取(大小通常小于 10k)。

您可能会争辩说,如今这些小文件并不重要,但我相信这种方法会导致代码效率低下。

第一个例子:

// Open file
using(var file = System.IO.File.OpenText(_LstFilename))
{
    // Read file
    while (!file.EndOfStream)
    {
        String line = file.ReadLine();

        // Ignore empty lines
        if (line.Length > 0)
        {
            // Create addon
            T addon = new T();
            addon.Load(line, _BaseDir);

            // Add to collection
            collection.Add(addon);
        }
    }
}

第二个例子:

// Open file
using (var file = System.IO.File.OpenText(datFile))
{
    // Compile regexs
    Regex nameRegex = new Regex("IDENTIFY (.*)");

    while (!file.EndOfStream)
    {
        String line = file.ReadLine();

        // Check name
        Match m = nameRegex.Match(line);
        if (m.Success)
        {
            _Name = m.Groups[1].Value;

            // Remove me when other values are read
            break;
        }
    }
}

【问题讨论】:

  • 50K 甚至不足以将其放入大型对象堆中。当您的文件在兆字节(或更大)范围内而不是千字节范围内时,流式传输是有意义的。

标签: c# linq line


【解决方案1】:

您可以使用迭代器块轻松编写基于 LINQ 的行阅读器:

static IEnumerable<SomeType> ReadFrom(string file) {
    string line;
    using(var reader = File.OpenText(file)) {
        while((line = reader.ReadLine()) != null) {
            SomeType newRecord = /* parse line */
            yield return newRecord;
        }
    }
}

或者让乔恩开心:

static IEnumerable<string> ReadFrom(string file) {
    string line;
    using(var reader = File.OpenText(file)) {
        while((line = reader.ReadLine()) != null) {
            yield return line;
        }
    }
}
...
var typedSequence = from line in ReadFrom(path)
                    let record = ParseLine(line)
                    where record.Active // for example
                    select record.Key;

那么你有 ReadFrom(...) 作为一个懒惰评估的序列,没有缓冲,非常适合 Where 等。

注意,如果你使用OrderBy或标准GroupBy,它必须在内存中缓冲数据;如果您需要分组和聚合,“PushLINQ”有一些花哨的代码允许您对数据执行聚合但丢弃它(无缓冲)。乔恩的解释is here。

【讨论】:

  • 呸,关注点分离 - 将读取的行分离到单独的迭代器中,并使用正常投影:)
  • 好多了...虽然仍然是文件特定的;)
  • 我认为您的示例不会编译。 "file" 已经被定义为字符串参数,所以你不能在 using 块中声明。
  • 技术很棒,谢谢马克!如果它对任何人有帮助,我已经整理了一篇关于使用它来帮助在 linqpad 中读取 csv 的博客文章:developertipoftheday.com/2012/10/read-csv-in-linqpad.html
  • @JonSkeet Marc 上面提供的链接已失效,能否请您提供新链接
【解决方案2】:

读取一行并检查它是否为空比一直检查 EndOfStream 更简单。

但是,我在 MiscUtil 中也有一个 LineReader 类,这使得所有这一切变得更加简单 - 基本上它公开了一个文件(或将 Func&lt;TextReader&gt; 暴露为 IEnumerable&lt;string&gt;,这让您可以对其进行 LINQ 操作. 所以你可以这样做:

var query = from file in Directory.GetFiles("*.log")
            from line in new LineReader(file)
            where line.Length > 0
            select new AddOn(line); // or whatever

LineReader 的核心是IEnumerable&lt;string&gt;.GetEnumerator 的实现:

public IEnumerator<string> GetEnumerator()
{
    using (TextReader reader = dataSource())
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            yield return line;
        }
    }
}

几乎所有其他来源只是提供灵活的设置dataSource(即Func&lt;TextReader&gt;)的方法。

【讨论】:

  • 如何关闭文件?并释放资源?
  • @dc7a9163d9:using 语句已经这样做了 - dataSource() 调用将打开文件,因此它将在 using 语句的末尾处理。
【解决方案3】:

从 .NET 4.0 开始,File.ReadLines() 方法可用。

int count = File.ReadLines(filepath).Count(line => line.StartsWith(">"));

【讨论】:

    【解决方案4】:

    注意:您需要注意IEnumerable&lt;T&gt; 解决方案,因为它会导致文件在处理期间处于打开状态。

    例如,用 Marc Gravell 的回应:

    foreach(var record in ReadFrom("myfile.csv")) {
        DoLongProcessOn(record);
    }
    

    文件将在整个处理过程中保持打开状态。

    【讨论】:

    • 没错,但是“文件打开很长时间,但没有缓冲”通常比“大量内存长时间占用”要好
    • 确实如此——但基本上你有三个选择:一次性加载大量文件(大文件失败);保持文件打开(如您所述);定期重新打开文件(有许多问题)。在很多很多情况下,我认为流式传输并保持文件打开是最好的解决方案。
    • 是的,保持文件打开可能是更好的解决方案,但您只需要远离暗示
    • 对不起,Marc 的名字打错了
    • 这绝对是一种潜在的意外副作用,但我也同意 Jon 的观点,因为它听起来确实是最好的解决方案。
    【解决方案5】:

    感谢大家的回答!我决定混合使用,主要关注 Marc,因为我只需要从文件中读取行。我猜你可能会说到处都需要分开,但是,生命太短暂了!

    关于保持文件打开,在这种情况下这不会成为问题,因为代码是桌面应用程序的一部分。

    最后我注意到你们都使用小写字符串。我知道在 Java 中大写和非大写字符串之间存在区别,但我认为在 C# 中小写字符串只是对大写字符串的引用?

    public void Load(AddonCollection<T> collection)
    {
        // read from file
        var query =
            from line in LineReader(_LstFilename)
            where line.Length > 0
            select CreateAddon(line);
    
        // add results to collection
        collection.AddRange(query);
    }
    
    protected T CreateAddon(String line)
    {
        // create addon
        T addon = new T();
        addon.Load(line, _BaseDir);
    
        return addon;
    }
    
    protected static IEnumerable<String> LineReader(String fileName)
    {
        String line;
        using (var file = System.IO.File.OpenText(fileName))
        {
            // read each line, ensuring not null (EOF)
            while ((line = file.ReadLine()) != null)
            {
                // return trimmed line
                yield return line.Trim();
            }
        }
    }
    

    【讨论】:

    • 为什么要将集合传递给 Load 方法?如果您要这样做,至少将其称为 LoadInto ;)
    猜你喜欢
    • 2022-01-25
    • 2010-12-24
    • 1970-01-01
    • 1970-01-01
    • 2014-06-21
    • 2012-06-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多