如何使用C#读取大型文本文件并跟踪前几行的信息?

如何使用C#读取大型文本文件并跟踪前几行的信息?,c#,.net,memory,memory-management,C#,.net,Memory,Memory Management,(这个问题是对现实生活场景的改编,我简化了问题,使其易于理解,否则这个问题将长达10000行) 我有一个以管道分隔的文本文件,如下所示(文件中没有标题): 引用是可选的,是此文本文件中另一个条目的Id。具有引用的条目被视为该引用的“子项”,该引用是其父项。我需要验证文件中的每个父项,验证的内容是它的子项的TotalAmount之和应该等于父项的总金额。在文件中,父母可以是第一个,也可以在他们的孩子之前,比如Id为9的条目,它位于孩子之后 在提供的文件中,Id为1的条目是有效的,因为它的子项(Id

(这个问题是对现实生活场景的改编,我简化了问题,使其易于理解,否则这个问题将长达10000行)

我有一个以管道分隔的文本文件,如下所示(文件中没有标题):

引用是可选的,是此文本文件中另一个条目的Id。具有引用的条目被视为该引用的“子项”,该引用是其父项。我需要验证文件中的每个父项,验证的内容是它的子项的TotalAmount之和应该等于父项的总金额。在文件中,父母可以是第一个,也可以在他们的孩子之前,比如Id为9的条目,它位于孩子之后

在提供的文件中,Id为1的条目是有效的,因为它的子项(Id 3和4)的总数是10000,Id为2的条目是无效的,因为它的子项(Id 5和6)的总数是20000

对于这样的小文件,我可以将所有内容解析为这样的对象(伪代码,我现在没有办法运行它):

类条目
{
公共int Id{get;set;}
公共整数TotalAmout{get;set;}
公共int引用{get;set;}
}
类验证器
{
public void Validate()
{
列表条目=GetEntriesFromFile(@“C:\entries.txt”);
foreach(分录中的var分录)
{
var children=entries.Where(e=>e.Reference==entry.Id).ToList();
如果(children.Count>0)
{
var sum=children.sum(e=>e.TotalAmout);
如果(总和==条目的总数)
{
WriteLine(“Id为{0}的条目有效”,Entry.Id);
}
其他的
{
WriteLine(“Id为{0}的条目无效”,Entry.Id);
}
}
其他的
{
WriteLine(“Id为{0}的条目有效”,Entry.Id);
}
}
}
公共列表GetEntriesFromFile(字符串文件)
{
var entries=新列表();
使用(var r=新的StreamReader(文件))
{
而(!r.EndOfStream)
{
var line=r.ReadLine();
var splited=line.Split(“|”);
var entry=新条目();
entry.Id=int.Parse(拆分为[0]);
entry.TotalAmout=int.Parse(拆分为[1]);
如果(拆分的长度==3)
{
entry.Reference=int.Parse(拆分为[2]);
}
条目。添加(条目);
}
}
返回条目;
}
}
问题是我处理的是大文件(10GB),这会加载到内存中的许多对象

public class Entry
{
    public int Id { get; set; }
    public int TotalAmount { get; set; }
    public int? Reference { get; set; }
}

public static class EntryValidator
{
    public static void Validate(string file)
    {
        var entries = GetEntriesFromFile(file);
        var childAmounts = new Dictionary<int, int>();
        var nonChildAmounts = new Dictionary<int, int>();

        foreach (var e in entries)
        {
            if (e.Reference is int p)
                childAmounts.AddOrUpdate(p, e.TotalAmount, (_, n) => n + e.TotalAmount);
            else
                nonChildAmounts[e.Id] = e.TotalAmount;
        }

        foreach (var id in nonChildAmounts.Keys)
        {
            var expectedTotal = nonChildAmounts[id];

            if (childAmounts.TryGetValue(id, out var childTotal) &&
                childTotal != expectedTotal)
            {
                Console.WriteLine($"Entry with Id {id} is INVALID");
            }
            else
            {
                Console.WriteLine($"Entry with Id {id} is valid");
            }
        }
    }

    private static IEnumerable<Entry> GetEntriesFromFile(string file)
    {
        foreach (var line in File.ReadLines(file))
            yield return GetEntryFromLine(line);
    }

    private static Entry GetEntryFromLine(string line)
    {
        var parts = line.Split('|');

        var entry = new Entry
                    {
                        Id = int.Parse(parts[0]),
                        TotalAmount = int.Parse(parts[1])
                    };

        if (parts.Length == 3)
            entry.Reference = int.Parse(parts[2]);

        return entry;
    }
}
性能本身在这里不是一个问题。例如,我知道我可以使用字典而不是
Where()
方法。我现在唯一的问题是在不将所有内容加载到内存的情况下执行验证,我不知道如何执行,因为文件底部的条目可能引用了顶部的条目,因此我需要跟踪所有内容


因此,我的问题是:可以跟踪文本文件中的每一行,而无需将其信息加载到内存中吗?

由于性能不是问题,因此我将通过以下方式进行处理:

首先,我会对文件进行排序,这样所有的家长都会排在孩子前面。有一些经典的方法可以对大量外部数据进行排序,请参见


在这之后,任务变得非常简单:读取父数据,记住它,逐个读取和求和子数据,比较,重复。

由于性能不是这里的问题,我将以以下方式处理此问题:

首先,我会对文件进行排序,这样所有的家长都会排在孩子前面。有一些经典的方法可以对大量外部数据进行排序,请参见


在此之后,任务变得非常简单:读取父数据,记住它,逐个读取和求和子数据,比较,重复。

您真正需要保存在内存中的是每个非子实体的预期总数,以及每个父实体的子总数的运行总和。如果您使用
File.ReadLines
API,您可以在文件上进行流式处理,并在处理完后“忘记”每一行。因为行是按需读取的,所以不必将整个文件保存在内存中

public class Entry
{
    public int Id { get; set; }
    public int TotalAmount { get; set; }
    public int? Reference { get; set; }
}

public static class EntryValidator
{
    public static void Validate(string file)
    {
        var entries = GetEntriesFromFile(file);
        var childAmounts = new Dictionary<int, int>();
        var nonChildAmounts = new Dictionary<int, int>();

        foreach (var e in entries)
        {
            if (e.Reference is int p)
                childAmounts.AddOrUpdate(p, e.TotalAmount, (_, n) => n + e.TotalAmount);
            else
                nonChildAmounts[e.Id] = e.TotalAmount;
        }

        foreach (var id in nonChildAmounts.Keys)
        {
            var expectedTotal = nonChildAmounts[id];

            if (childAmounts.TryGetValue(id, out var childTotal) &&
                childTotal != expectedTotal)
            {
                Console.WriteLine($"Entry with Id {id} is INVALID");
            }
            else
            {
                Console.WriteLine($"Entry with Id {id} is valid");
            }
        }
    }

    private static IEnumerable<Entry> GetEntriesFromFile(string file)
    {
        foreach (var line in File.ReadLines(file))
            yield return GetEntryFromLine(line);
    }

    private static Entry GetEntryFromLine(string line)
    {
        var parts = line.Split('|');

        var entry = new Entry
                    {
                        Id = int.Parse(parts[0]),
                        TotalAmount = int.Parse(parts[1])
                    };

        if (parts.Length == 3)
            entry.Reference = int.Parse(parts[2]);

        return entry;
    }
}
公共类条目
{
公共int Id{get;set;}
公共整数总数{get;set;}
公共int?引用{get;set;}
}
公共静态类入口验证器
{
公共静态无效验证(字符串文件)
{
var entries=GetEntriesFromFile(文件);
var childAmounts=新字典();
var nonChildAmounts=新字典();
foreach(条目中的变量e)
{
如果(e.参考为int p)
childAmounts.AddOrUpdate(p,e.TotalAmount,(_,n)=>n+e.TotalAmount);
其他的
非Childamounts[e.Id]=e.TotalAmount;
}
foreach(非childamounts.key中的变量id)
{
var expectedTotal=非奇数[id];
if(childAmounts.TryGetValue(id,out var childTotal)&&
儿童总数!=预期总数)
{
WriteLine($“Id为{Id}的条目无效”);
}
其他的
{
WriteLine($“Id为{Id}的条目有效”);
}
}
}
私有静态IEnumerable GetEntriesFromFile(字符串文件)
{
foreach(文件中的var行。ReadLines(文件))
收益返回GetEntryFromLine(行);
}
私有静态条目GetEntryFromLine(stri
public class Entry
{
    public int Id { get; set; }
    public int TotalAmount { get; set; }
    public int? Reference { get; set; }
}

public static class EntryValidator
{
    public static void Validate(string file)
    {
        var entries = GetEntriesFromFile(file);
        var childAmounts = new Dictionary<int, int>();
        var nonChildAmounts = new Dictionary<int, int>();

        foreach (var e in entries)
        {
            if (e.Reference is int p)
                childAmounts.AddOrUpdate(p, e.TotalAmount, (_, n) => n + e.TotalAmount);
            else
                nonChildAmounts[e.Id] = e.TotalAmount;
        }

        foreach (var id in nonChildAmounts.Keys)
        {
            var expectedTotal = nonChildAmounts[id];

            if (childAmounts.TryGetValue(id, out var childTotal) &&
                childTotal != expectedTotal)
            {
                Console.WriteLine($"Entry with Id {id} is INVALID");
            }
            else
            {
                Console.WriteLine($"Entry with Id {id} is valid");
            }
        }
    }

    private static IEnumerable<Entry> GetEntriesFromFile(string file)
    {
        foreach (var line in File.ReadLines(file))
            yield return GetEntryFromLine(line);
    }

    private static Entry GetEntryFromLine(string line)
    {
        var parts = line.Split('|');

        var entry = new Entry
                    {
                        Id = int.Parse(parts[0]),
                        TotalAmount = int.Parse(parts[1])
                    };

        if (parts.Length == 3)
            entry.Reference = int.Parse(parts[2]);

        return entry;
    }
}
public static class DictionaryExtensions
{
    public static TValue AddOrUpdate<TKey, TValue>(
        this IDictionary<TKey, TValue> dictionary,
        TKey key,
        TValue addValue,
        Func<TKey, TValue, TValue> updateCallback)
    {
        if (dictionary == null)
            throw new ArgumentNullException(nameof(dictionary));
        if (updateCallback == null)
            throw new ArgumentNullException(nameof(updateCallback));

        if (dictionary.TryGetValue(key, out var value))
            value = updateCallback(key, value);
        else
            value = addValue;

        dictionary[key] = value;

        return value;
    }
}