C#Linq在内存中将Hashset <t>与来自文件流的IEnumerable <t>进行交叉</t> </t>

时间:2012-07-25 00:06:11

标签: c# linq memory file-io

我有这个类暴露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具有许多其他属性(因此具有相当大的“内存/存储明智”),Id属性(由{2}组成的Key对象“部分”)。为了完整性,这看起来像:

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()文档告诉我:

  

枚举此方法返回的对象时,相交   枚举 first ,收集该序列的所有不同元素。   然后它枚举 second ,标记两者中出现的那些元素   序列。 最后,标记的元素按顺序生成   他们被收集了。

(强调我的)

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

显然,我想要阻止的是

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

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

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


P.S。我只是被一个关于在床上使用Linq用于此目的的脑波所击中,在我弄清楚之前无法入睡。不幸的是,我正在度假,距离一个体面的Visual Studio实例只有几英里,只是简单地测试一下。这将不得不等到我的假期之后(所以失误说......我们会看到......)smiley

1 个答案:

答案 0 :(得分:2)

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

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

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

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

请参阅我的Edulinq blog post on Intersect,了解其实际功能的详细信息。

在TL; DR意义上,它是:

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

在我们去的时候从集合中删除项目的事实会阻止相同的元素被放置两次(即使它在firstsecond中出现多次,因为它是一个设定)。

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

var result = streamingRecordsFromFile.Intersect(smallCollectionInMemory);