1

我有一个公开IEnumerable<Record>如下的类(省略了实现细节):

public class SomeFileReader() {
    public IEnumerable<Record> Records()
    {
        using (StreamReader sr = new StreamReader(this.Path, this.Encoding, true))
        {
            var hdr = this.HeaderParser.Parse(sr.ReadLine());  //Parse, but further ignore header (the HeaderParser might throw though)
            while (!sr.EndOfStream)
                yield return this.RecordParser.Parse(sr.ReadLine()) as Record;
        }
    }

Record除了许多其他属性(因此是相当大的“内存/存储方式”)之外,A还具有一个Id属性(这是一个Key由 2 个“部分”组成的对象)。为了完整起见,这看起来像:

public class Key : IEquatable<Key>
{
    public string OperatorCode { get; set; }
    public string Key { get; set; }

    public bool Equals(Key other)
    {
        return (this.OperatorCode.Equals(other.OperatorCode, StringComparison.OrdinalIgnoreCase))
            && (this.Key.Equals(other.Key, StringComparison.OrdinalIgnoreCase));
    }
}

该文件包含“键顺序”中的记录,因此(保证)按记录的ID磁盘排序。

在内存中,我还有一些HashSet<Key>我想从SomeFileReader. 目前我的测试文件只有几兆字节,但我预计在不久的将来它会变得非常大。此时,我只是将整个文件读入内存Dictionary<Key, Record>,以便从“待处理”记录的“列表”中轻松/快速地检索我想要处理的特定记录。这将类似于:

var recordsfromfile = MyFileImporter.Records().ToDictionary(k => k.Key.Key);

当然,一旦文件增长(太大),这将是一个问题。

但是由于我正在公开IEnumerable<Record>我在想...我不应该将文件完全读入内存,因为记录是按关键顺序排列的。一个简单的Intersect()与我的待处理密钥“列表”就足够了。Key已经实现了IEquatable,如果我需要一个根本IEQualityComparer<Key>不难实现的。但我(想我)离题了。。

Intersect()文档告诉我:

当枚举此方法返回的对象时,Intersect 枚举first,收集该序列的所有不同元素。然后它枚举second,标记出现在两个序列中的那些元素。最后,标记的元素按照它们被收集的顺序产生。

(强调我的)

所以,如果我理解正确,如果first是我IEnumerable<Record>的文件仍将完全读入内存。即使它second与我的“待处理”“列表”完全匹配,仍然会被读入内存,这仍然可能是非常大量的数据。还是我误读了文档,这是“终于”让我绊倒和/或我误解了文档?

显然,我想要阻止的是

  • a)不将大量数据读取到内存中,其唯一目的是一个接一个地处理其中的一些记录,之后我不关心这些记录(例如,处理会将结果写到其他地方)
  • b)不要(重新)为我的“待处理”“列表”中的每条记录一次又一次地打开同一个文件(所以我要小心不要重置我的迭代器)

长话短说; 会Intersect()做我想做的事吗?我应该使用其他方法吗?嵌套for循环?关于如何有效处理这个问题的任何其他想法?

编辑:更新以明确“要处理的密钥列表”实际上是一个HashSet<Key>.


Ps 我刚刚被一个关于在床上使用 Linq 用于此目的的脑电波击中,在我弄清楚之前无法入睡。不幸的是,我正在度假,距离一个体面的 Visual Studio 实例数英里远,只是为了简单地测试一下。那将不得不等到我休假之后(所以小姐们说......我们会看到......)笑脸

4

1 回答 1

2

编辑:我怀疑你真的想要:

var records = new SomeFileReader().Records()
                                  .Where(record => keys.Contains(record.Key));

foreach (var record in records)
{
    Process(record);
}

Intersect恐怕文档是错误的。它实际上首先枚举second,收集其中的所有内容......然后是流first,产生任何相交的值。

它也不会等到看到所有元素后才生成它们。有关它实际作用的更多详细信息,请参阅我的Edulinq 博客文章。Intersect

在 TL;DR 意义上,它是:

  • 创建一个HashSet<T>second
  • 迭代first
    • 对于每个项目,尝试将其从集合中移除
    • 如果它集合中,则让出它;否则,不要

first项目从集合中移除的事实阻止了相同的元素被生成两次(即使它在两个和中都出现了不止一次second,因为它是一个集合)。

基本上,我认为只要你颠倒操作数的顺序就可以了,所以你这样做:

var result = streamingRecordsFromFile.Intersect(smallCollectionInMemory);
于 2012-07-25T00:10:48.747 回答