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