【问题标题】:Read x number of lines of a file at a time C#一次读取文件的 x 行 C#
【发布时间】:2016-11-16 06:29:14
【问题描述】:

我想一次读取和处理 10+ 行 GB 文件,但是直到最后都没有找到解决方案来吐出 10 行。

我最后一次尝试是:

        int n = 10;
        foreach (var line in File.ReadLines("path")
            .AsParallel().WithDegreeOfParallelism(n))
        {
            System.Console.WriteLine(line);
            Thread.Sleep(1000);
        }

我见过使用缓冲区大小的解决方案,但我想读取整行。

【问题讨论】:

  • 你在最后 10 行之后?
  • 你能不能不使用.Take 函数来做这件事,也许你可以看看这个并让它为你工作.. 一次 10 行会带你永远.. 为什么不设置例如 300 的行查看此链接 - stackoverflow.com/questions/11326564/…
  • 您能否说明您对结果的期望?
  • 所以你想读取 10 行,处理它们,输出结果,然后读取接下来的 10 行,等等?你想用多线程处理每组 10 行吗?
  • 投票以“不清楚你在问什么”而关闭,因为 OP 没有回应查询。

标签: c# file io stream


【解决方案1】:

此方法会从您的文件中创建“页面”行。

public static IEnumerable<string[]> ReadFileAsLinesSets(string fileName, int setLen = 10)
{
    using (var reader = new StreamReader(fileName))
        while (!reader.EndOfStream)
        {
            var set = new List<string>();
            for (var i = 0; i < setLen && !reader.EndOfStream; i++)
            {
                set.Add(reader.ReadLine());
            }
            yield return set.ToArray();
        }
}

...更有趣的版本...

class Example
{
    static void Main(string[] args)
    {
        "YourFile.txt".ReadAsLines()
                      .AsPaged(10)
                      .Select(a=>a.ToArray()) //required or else you will get random data since "WrappedEnumerator" is not thread safe
                      .AsParallel()
                      .WithDegreeOfParallelism(10)
                      .ForAll(a =>
        {
            //Do your work here.
            Console.WriteLine(a.Aggregate(new StringBuilder(), 
                                          (sb, v) => sb.AppendFormat("{0:000000} ", v), 
                                          sb => sb.ToString()));
        });
    }
}

public static class ToolsEx
{

    public static IEnumerable<IEnumerable<T>> AsPaged<T>(this IEnumerable<T> items,
                                                              int pageLength = 10)
    {
        using (var enumerator = new WrappedEnumerator<T>(items.GetEnumerator()))
            while (!enumerator.IsDone)
                yield return enumerator.GetNextPage(pageLength);
    }

    public static IEnumerable<T> GetNextPage<T>(this IEnumerator<T> enumerator,
                                                     int pageLength = 10)
    {
        for (var i = 0; i < pageLength && enumerator.MoveNext(); i++)
            yield return enumerator.Current;
    }

    public static IEnumerable<string> ReadAsLines(this string fileName)
    {
        using (var reader = new StreamReader(fileName))
            while (!reader.EndOfStream)
                yield return reader.ReadLine();
    }
}

internal class WrappedEnumerator<T> : IEnumerator<T>
{
    public WrappedEnumerator(IEnumerator<T> enumerator)
    {
        this.InnerEnumerator = enumerator;
        this.IsDone = false;
    }

    public IEnumerator<T> InnerEnumerator { get; private set; }
    public bool IsDone { get; private set; }

    public T Current { get { return this.InnerEnumerator.Current; } }
    object System.Collections.IEnumerator.Current { get { return this.Current; } }

    public void Dispose()
    {
        this.InnerEnumerator.Dispose();
        this.IsDone = true;
    }

    public bool MoveNext()
    {
        var next = this.InnerEnumerator.MoveNext();
        this.IsDone = !next;
        return next;
    }

    public void Reset()
    {
        this.IsDone = false;
        this.InnerEnumerator.Reset();
    }
}

【讨论】:

  • 不完全确定这会起作用,因为 .Net 有一个最大 2Gb 的内存页面大小来读取整个文件,如果它是多 Gb 会很快达到这个限制
  • 它只会读入你调用的内存。如果你使用类似.AsParallel().WithDegreeOfParallelism(n)) 的东西,它应该只有n 在任何给定时间加载的页面数。
  • 是的,可以让它变得更加懒惰,所以即使是 IEnumerable 中的内部集合......但这比我想写的 SO 答案要复杂一些。 ……至少现在是这样。
  • 在第一个版本中,它应该是 'for (var i = 0; i
【解决方案2】:

默认行为是一口气读完所有行,如果你想读的少一点,你需要更深入地研究它是如何读的,然后得到一个StreamReader,然后你就可以控制阅读了进程

        using (StreamReader sr = new StreamReader(path)) 
        {
            while (sr.Peek() >= 0) 
            {
                Console.WriteLine(sr.ReadLine());
            }
        }

它还有一个ReadLineAsync 方法会返回一个任务

如果您将这些任务包含在 ConcurrentBag 中,您可以非常轻松地保持处理一次运行 10 行。

var bag =new ConCurrentBag<Task>();
using (StreamReader sr = new StreamReader(path))
{
    while(sr.Peek() >=0)
    {
        if(bag.Count < 10)
        {
            Task processing = sr.ReadLineAsync().ContinueWith( (read) => {
                string s = read.Result;//EDIT Removed await to reflect Scots comment
                //process line
            });
            bag.Add(processing);
        }
        else
        {
            Task.WaitAny(bag.ToArray())
            //remove competed tasks from bag
        }
    }
}

请注意,此代码仅供参考,请勿按原样使用;

如果你想要的只是最后十行,那么你可以通过这里的解决方案得到它 How to read a text file reversely with iterator in C#

【讨论】:

  • 不需要await read,read 需要处于完成状态(也不会编译,因为匿名方法未标记为异步)。只需做一个read.Result
  • 当有 10 个任务处理时,这会在 sr.Peek() 上形成一个紧密循环吗?如何从包中取出已完成的任务?
  • 我总是在谨慎使用线程方面犯错,等待一些很少受到伤害的事情,但假设某些事情已经完成但它还没有完成可能会导致噩梦,还要注意我提到需要在文本中添加异步.我正在向他们展示如何完成不为他们做的任务
  • @JimMischel 有几种方法可以删除任务,最简单的方法是使用 where(t=> t.IsCompleted) 的 linq,然后删除结果,或者您可以在进程中添加 continue with (但不是它的一部分)从包中删除任务也会起作用,至于偷看我不完全确定我从未真正尝试过这不会伤害到处理阶段添加空检查然后使用要结束循环,您可能会浪费几个周期来处理空值
  • 为每一行开始一个新的Task 似乎效率很低。猜猜这取决于每行需要多少处理。
猜你喜欢
  • 2021-05-08
  • 2014-12-10
  • 2018-06-21
  • 2018-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-14
相关资源
最近更新 更多