2012-08-10 100 views
4

我有以下代码来读取一个大文件,说超过一百万行。我正在使用Parallel和Linq方法。有没有更好的方法来做到这一点?如果是,那么如何?在C#中读取csv文件的最佳方法,以提高时间效率

 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); 
     } 
+0

一般而言,Parallell是用于CPU绑定任务的。当你锁定Parallell.ForEach时,你不会有任何可扩展性,因此我看不出它对你有很大的帮助。 – FuleSnabel 2012-08-10 07:14:16

+0

最好在[codereview](http://codereview.stackexchange.com/faq)上提问。 – 2012-08-10 07:48:36

回答

2

您可以将内置的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(); 
      } 
     } 
    } 
} 

thisthis以备将来参考。

0

你应该看看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 
      } 
} 
+0

'csv.Configuration.ClassMapping (); '=>关于'LogHeaderMap'的混淆。它是什么?它与'LogHeader'类有关吗?如果是这样,关系是什么? – 2017-11-16 01:28:54

0

最近我遇到了为了同样目的尽快解析大型CSV文件的问题:数据聚合和度量计算(在我的情况下最终目标是数据透视表生成)。我测试了大多数流行的CSV阅读器,但发现它们并不适用于解析包含数百万行或更多行的CSV文件; JoshClose的CsvHelper速度很快,但最终我能够将CSV以流的速度以2x - 4倍的速度处理!

我的方法是基于2个假设:

  • 避免创建字符串时可能因为这是存储器和CPU废物(= GC增加有效载荷)。除此之外,解析器结果可以表示为仅保存缓冲区中的开始和结束位置的一组“字段值”描述符+一些元数据(引用值标志,值内的双引号数),并且字符串值仅在需要。
  • 使用循环char []缓冲区读取csv行以避免过多的数据复制
  • 没有抽象,最小的方法调用 - 这使得有效的JIT优化(比如避免数组长度检查)。没有LINQ,没有迭代器(foreach) - 因为for效率更高。

现实生活中使用编号(由200MB的CSV文件中的数据透视表,17列,只有3列被用来建立一个交叉表):

  • 我自定义的CSV读者:〜1.9s
  • CsvHelper :〜6.1s
相关问题