我有一个大文件中要读取以下代码,比如超过一百万行。我正在使用并行和Linq方法。有没有更好的方法?如果是,那怎么做?
private static void ReadFile()
{
float floatTester = 0;
List<float[]> result = File.ReadLines(@"largedata.csv")
.Where(l => !string.IsNullOrWhiteSpace(l))
.Select(l => new { Line = l, Fields = l.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) })
.Select(x => x.Fields
.Where(f => Single.TryParse(f, out floatTester))
.Select(f => floatTester).ToArray())
.ToList();
// now get your totals
int numberOfLinesWithData = result.Count;
int numberOfAllFloats = result.Sum(fa => fa.Length);
MessageBox.Show(numberOfAllFloats.ToString());
}
private static readonly char[] Separators = { ',', ' ' };
private static void ProcessFile()
{
var lines = File.ReadAllLines("largedata.csv");
var numbers = ProcessRawNumbers(lines);
var rowTotal = new List<double>();
var totalElements = 0;
foreach (var values in numbers)
{
var sumOfRow = values.Sum();
rowTotal.Add(sumOfRow);
totalElements += values.Count;
}
MessageBox.Show(totalElements.ToString());
}
private static List<List<double>> ProcessRawNumbers(IEnumerable<string> lines)
{
var numbers = new List<List<double>>();
/*System.Threading.Tasks.*/
Parallel.ForEach(lines, line =>
{
lock (numbers)
{
numbers.Add(ProcessLine(line));
}
});
return numbers;
}
private static List<double> ProcessLine(string line)
{
var list = new List<double>();
foreach (var s in line.Split(Separators, StringSplitOptions.RemoveEmptyEntries))
{
double i;
if (Double.TryParse(s, out i))
{
list.Add(i);
}
}
return list;
}
private void button1_Click(object sender, EventArgs e)
{
Stopwatch stopWatchParallel = new Stopwatch();
stopWatchParallel.Start();
ProcessFile();
stopWatchParallel.Stop();
// Get the elapsed time as a TimeSpan value.
TimeSpan ts = stopWatchParallel.Elapsed;
// Format and display the TimeSpan value.
string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
ts.Hours, ts.Minutes, ts.Seconds,
ts.Milliseconds / 10);
MessageBox.Show(elapsedTime);
Stopwatch stopWatchLinQ = new Stopwatch();
stopWatchLinQ.Start();
ReadFile();
stopWatchLinQ.Stop();
// Get the elapsed time as a TimeSpan value.
TimeSpan ts2 = stopWatchLinQ.Elapsed;
// Format and display the TimeSpan value.
string elapsedTimeLinQ = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
ts2.Hours, ts.Minutes, ts.Seconds,
ts2.Milliseconds / 10);
MessageBox.Show(elapsedTimeLinQ);
}
我遇到了出于相同目的尽可能快地解析大型CSV文件的问题:数据聚合和指标计算(在我的情况下,最终目标是生成数据透视表)。我测试了最流行的 CSV 阅读器,但发现它们不是为解析具有数百万行或更多行的 CSV 文件而设计的;JoshClose 的 CsvHelper 速度很快,但最终我能够以 2-4 倍的速度将 CSV 作为流进行处理!
我的方法基于两个假设:
- 尽可能避免创建字符串,因为这会浪费内存和 CPU(= 增加 GC 有效负载)。取而代之的是,解析器结果可以表示为一组"字段值"描述符,这些描述符仅保留缓冲区中的开始和结束位置 + 一些元数据(引号值标志,值内的双引号数量),并且字符串值仅在需要时构造。
- 使用循环 char[] 缓冲区读取 CSV 行,避免过多的数据复制
- 没有抽象,最少的方法调用 - 这样可以实现有效的 JIT 优化(例如,避免数组长度检查)。没有LINQ,没有迭代器(
foreach
) - 因为for
效率更高。
实际使用数字(数据透视表由 200MB CSV 文件,17 列,仅使用 3 列来构建交叉表):
- 我的自定义 CSV 阅读器:~1.9 秒
- Csv助手:~6.1s
---更新---
我已经在 github 上发布了我的库,如上所述工作:https://github.com/nreco/csv
Nuget 包:https://www.nuget.org/packages/NReco.Csv/
你可以为此内置 OleDb。
public void ImportCsvFile(string filename)
{
FileInfo file = new FileInfo(filename);
using (OleDbConnection con =
new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source="" +
file.DirectoryName + "";
Extended Properties='text;HDR=Yes;FMT=Delimited(,)';"))
{
using (OleDbCommand cmd = new OleDbCommand(string.Format
("SELECT * FROM [{0}]", file.Name), con))
{
con.Open();
// Using a DataTable to process the data
using (OleDbDataAdapter adp = new OleDbDataAdapter(cmd))
{
DataTable tbl = new DataTable("MyTable");
adp.Fill(tbl);
//foreach (DataRow row in tbl.Rows)
//Or directly make a list
List<DataRow> list = dt.AsEnumerable().ToList();
}
}
}
}
请参阅此和此内容以供进一步参考。
查看快速CSV阅读器。
你应该看看 CsvHelper => https://github.com/JoshClose/CsvHelper/
它允许您将.csv文件与类映射,因此您可以将.csv文件用作对象。尝试一下,然后尝试应用并行操作以查看是否有更好的性能。
这是我为一个项目准备的示例代码:
using (var csv = new CsvReader(new StreamReader(filePath, Encoding.Default)))
{
csv.Configuration.Delimiter = ';';
csv.Configuration.ClassMapping<LogHeaderMap, LogHeader>();
var data = csv.GetRecords<LogHeader>();
foreach (var entry in data.OrderByDescending(x => x.Date))
{
//process
}
}