【问题标题】:Best way to read csv file in C# to improve time efficiency在 C# 中读取 csv 文件以提高时间效率的最佳方法
【发布时间】:2012-08-07 10:20:09
【问题描述】:

我有以下代码可以读取一个大文件,比如超过一百万行。我正在使用 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);
        }

【问题讨论】:

  • 通常 Parallell 用于 CPU 绑定任务。当您锁定 Parallell.ForEach 时,您无法获得可扩展性,因此我看不出它确实对您有很大帮助。
  • 最好在codereview问这个问题。

标签: c# performance linq parallel-processing system.diagnostics


【解决方案1】:

您可以为此使用内置的 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 以获取更多参考。

【讨论】:

  • 看起来不错,它可以节省我的培根,但有一个更新。 Microsoft.Jet.OLEDB.4.0 在 2002 年被弃用,后来被 KB 2017 打破。使用“Microsoft.ACE.OLEDB.12.0”。这个我已经成功使用Excel 8.0了。
【解决方案2】:

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

【讨论】:

  • csv.Configuration.ClassMapping&lt;LogHeaderMap, LogHeader&gt;(); => 对LogHeaderMap 感到困惑。它是什么?它与LogHeader class 有关吗?如果有,是什么关系?
【解决方案3】:

查看Fast CSV Reader

【讨论】:

    【解决方案4】:

    最近,我遇到了为同一目的尽可能快地解析大型 CSV 文件的问题:数据聚合和指标计算(在我的案例中,最终目标是生成数据透视表)。我测试了最流行的 CSV 阅读器,但发现它们并不是为解析数百万行或更多行的 CSV 文件而设计的; JoshClose 的 CsvHelper 速度很快,但最终我能够以 2 到 4 倍的速度将 CSV 作为流处理!

    我的方法基于两个假设:

    • 尽可能避免创建字符串,因为这会浪费内存和 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/

    【讨论】:

    • 你是否在 GitHub 上发布了你的库?
    • @rburte 还没有,你对这个库感兴趣吗?代码稳定并且在我的产品(SeekTable)的 prod 环境中运行良好,所以如果其他人也需要这个超快速/内存高效的 CSV 解析器,我可以将它发布在 github/nuget 上。
    • 是的,我将不胜感激。我的工作流程的一部分需要加载和扫描,所以......目前这是一个僵局。
    • 您是否计划使用新的 Span 和 Memory 类来更新您的库?
    • @AkmalSalikhov 问题已经存在:github.com/nreco/csv/issues/1 实际上,这只是一种无需复制即可访问内部 char[] 缓冲区中字符的替代 API 方法(结果为 ReadOnlySpan),将偶尔这样做 - 或者有人可能会通过 PR 提出必要的更改。我不期望从中获得显着的性能提升——因为这可能只会有助于避免不必要的分配。内部解析器直接使用 char[] 缓冲区 + 索引,这与 Span 在后台所做的非常相似。
    猜你喜欢
    • 2015-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多