如何完全加载文件并处理记录 csvreader?
How to load file fully and process record csvreader?
我使用 CSV reader 发现解析数据需要很多时间。我如何才能将整个 csv 文件加载到内存中,然后逐条处理它,因为我必须对记录进行自定义映射。
TextReader tr = new StreamReader(File.Open(@"C:\MarketData\" + symbol + ".txt", FileMode.Open));
CsvReader csvr = new CsvReader(tr);
while (csvr.Read())
{
// do your magic
}
创建一个 class 正好 represents/mirrors 您的 CSV 文件。然后把所有的内容读入那个class的列表。以下片段来自 CsvHelper 的文档。
var csv = new CsvReader( textReader );
var records = csv.GetRecords<MyClass>().ToList();
重要的部分是 .ToList(),因为这会强制将所有数据加载到您的列表中,而不是在您访问它们时产生结果。
然后您可以对该列表执行额外的映射/提取,这将在内存中。
如果您已经在这样做,您可能会受益于通过 (ToHashSet()) 将 csv 加载到 HashSet 而不是 List 中。参见 HashSet vs List Performance
直接回答您的问题:您可以将文件完全加载到内存流中,然后使用 CsvReader 从该流中重新读取。同样,您可以为您的文件流创建一个更大的读取缓冲区,例如 15MB,这将一次性将整个文件读入缓冲区。我怀疑这些中的任何一个是否真的会提高 10MB 文件的性能。
找到真正的性能瓶颈:从磁盘读取文件内容的时间、将 CSV 解析为字段的时间,还是处理记录的时间?一个 10MB 的文件看起来很小。我正在使用自定义 csv reader 处理 250MB 以上的 csv 文件集,没有任何投诉。
如果处理是瓶颈并且您有多个可用线程并且您的 csv 文件格式不需要支持转义换行符,那么您可以将整个文件读入行列表 (System.IO.File.ReadAllLines / . ReadLines) 并使用不同的任务解析每一行。例如:
System.IO.File.ReadLines()
.Skip(1) // header line. Assume trusted to be correct.
.AsParallel()
.Select(ParseRecord) // RecordClass ParseRecord(string line)
.ForAll(ProcessRecord); // void ProcessRecord(RecordClass)
如果您有很多文件要解析,您可以在不同的任务中处理每个文件,并使用异步方法来最大化吞吐量。如果它们都来自同一个物理磁盘,那么您的里程数会有所不同,甚至可能比单线程方法更差。
更高级:
如果您知道您的文件只包含 8 位字符,那么您可以对字节数组进行操作并跳过 StreamReader 开销以将字节转换为字符。通过这种方式,您可以在一次调用中将整个文件读入字节数组并扫描换行符,假设不需要支持换行符转义。在这种情况下,可以通过多个线程扫描换行符,每个线程查看字节数组的一部分。
如果您不需要支持字段转义 (a,"b,c",d),那么您可以编写一个更快的解析器,只需寻找字段分隔符(通常是逗号)。如果这是一个瓶颈,您还可以将字段划分解析和字段内容解析拆分到线程中,尽管内存访问局部性可能会抵消任何好处。
在某些情况下,您可能不需要将字段解析为中间数据结构(例如双精度数、字符串),并且可以直接处理对字段 start/end 的引用并节省一些中间数据结构创建。
我使用 CSV reader 发现解析数据需要很多时间。我如何才能将整个 csv 文件加载到内存中,然后逐条处理它,因为我必须对记录进行自定义映射。
TextReader tr = new StreamReader(File.Open(@"C:\MarketData\" + symbol + ".txt", FileMode.Open));
CsvReader csvr = new CsvReader(tr);
while (csvr.Read())
{
// do your magic
}
创建一个 class 正好 represents/mirrors 您的 CSV 文件。然后把所有的内容读入那个class的列表。以下片段来自 CsvHelper 的文档。
var csv = new CsvReader( textReader );
var records = csv.GetRecords<MyClass>().ToList();
重要的部分是 .ToList(),因为这会强制将所有数据加载到您的列表中,而不是在您访问它们时产生结果。
然后您可以对该列表执行额外的映射/提取,这将在内存中。
如果您已经在这样做,您可能会受益于通过 (ToHashSet()) 将 csv 加载到 HashSet 而不是 List 中。参见 HashSet vs List Performance
直接回答您的问题:您可以将文件完全加载到内存流中,然后使用 CsvReader 从该流中重新读取。同样,您可以为您的文件流创建一个更大的读取缓冲区,例如 15MB,这将一次性将整个文件读入缓冲区。我怀疑这些中的任何一个是否真的会提高 10MB 文件的性能。
找到真正的性能瓶颈:从磁盘读取文件内容的时间、将 CSV 解析为字段的时间,还是处理记录的时间?一个 10MB 的文件看起来很小。我正在使用自定义 csv reader 处理 250MB 以上的 csv 文件集,没有任何投诉。
如果处理是瓶颈并且您有多个可用线程并且您的 csv 文件格式不需要支持转义换行符,那么您可以将整个文件读入行列表 (System.IO.File.ReadAllLines / . ReadLines) 并使用不同的任务解析每一行。例如:
System.IO.File.ReadLines()
.Skip(1) // header line. Assume trusted to be correct.
.AsParallel()
.Select(ParseRecord) // RecordClass ParseRecord(string line)
.ForAll(ProcessRecord); // void ProcessRecord(RecordClass)
如果您有很多文件要解析,您可以在不同的任务中处理每个文件,并使用异步方法来最大化吞吐量。如果它们都来自同一个物理磁盘,那么您的里程数会有所不同,甚至可能比单线程方法更差。
更高级:
如果您知道您的文件只包含 8 位字符,那么您可以对字节数组进行操作并跳过 StreamReader 开销以将字节转换为字符。通过这种方式,您可以在一次调用中将整个文件读入字节数组并扫描换行符,假设不需要支持换行符转义。在这种情况下,可以通过多个线程扫描换行符,每个线程查看字节数组的一部分。
如果您不需要支持字段转义 (a,"b,c",d),那么您可以编写一个更快的解析器,只需寻找字段分隔符(通常是逗号)。如果这是一个瓶颈,您还可以将字段划分解析和字段内容解析拆分到线程中,尽管内存访问局部性可能会抵消任何好处。
在某些情况下,您可能不需要将字段解析为中间数据结构(例如双精度数、字符串),并且可以直接处理对字段 start/end 的引用并节省一些中间数据结构创建。