簡體   English   中英

讀取C#中的csv文件以提高時間效率的最佳方法

[英]Best way to read csv file in C# to improve time efficiency

我有以下代碼可讀取一個大文件(例如,超過一百萬行)。 我正在使用Parallel和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);
        }

您可以為此內置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閱讀器

最近,我面臨一個問題,即出於相同的目的,需要盡快解析大型CSV文件:數據聚合和指標計算(在我的情況下,最終目標是數據透視表的生成)。 我測試了大多數流行的CSV閱讀器,但發現它們並不是為解析具有百萬行或更多行的CSV文件而設計的。 JoshClose的CsvHelper速度很快,但最終我能夠將CSV作為流處理的速度提高了2到4倍!

我的方法基於兩個假設:

  • 盡可能避免創建字符串,因為這會浪費內存和CPU(=增加GC負載)。 取而代之的是,解析器結果可以表示為“字段值”描述符的集合,這些描述符僅保存緩沖區中的開始和結束位置以及一些元數據(帶引號的值標志,內部雙引號的數量),並且僅在以下情況下構造字符串值:需要。
  • 使用循環char []緩沖區讀取csv行,以避免過多的數據復制
  • 無需抽象,只需最少的方法調用-這可以實現有效的JIT優化(例如,避免數組長度檢查)。 沒有LINQ,沒有迭代器( foreach ) -作為for效率要高得多。

實際使用情況數字(200MB CSV文件的數據透視表,17列,僅3列用於構建交叉表):

  • 我的自定義CSV閱讀器:〜1.9s
  • CsvHelper:〜6.1s

-更新-

我已經在github上發布了如上所述工作的庫: https : //github.com/nreco/csv

Nuget包: https ://www.nuget.org/packages/NReco.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
            }
 }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM