【问题标题】:C#: Reading Huge CSV FileC#:读取巨大的 CSV 文件
【发布时间】:2012-05-30 15:37:01
【问题描述】:

我正在解析一个 40MB 的 CSV 文件。

它现在工作得很好,而且很容易解析,我唯一的问题是性能,这当然是相当慢的。

我想知道是否有一种方法可以改进这一点,因为我只需要通过我找到的键找到然后停止循环,所以如果条目位于文件的开头,它会很快完成,但是如果它在最后,则需要一段时间。

我可以通过给它一个随机的起始线来平衡它,但算法仍然是 O(n)...所以我不确定它是否真的值得。

有什么方法可以改进我的顺序解析算法?

【问题讨论】:

  • 如果您可以按该键对文件进行排序,则可以使用二进制搜索大大提高速度。
  • 首先,对于 CSV 文件来说,40MB 并不是那么大。其次,您目前正在使用的显然需要这么长时间的代码是什么?我不认为它会那么慢,这意味着您要么有不切实际的期望,要么您的代码中存在一些更严重的问题。
  • 1 秒?为什么要快于一秒
  • 我只是想知道是否有更快的方法,因为我很好奇? :-(
  • 为什么不发布您的代码,以便人们提出一些建议。说“我怎样才能让它跑得更快?”不说 IT 是什么,就没有太多可做的事情。

标签: c# csv


【解决方案1】:

首先:“读取巨大的 CSV 文件”和“所以我正在解析一个 40MB 的 CSV 文件。”。我这里有 10+ 千兆字节的空间分隔文件 - 你会怎么称呼它们?

另外:文件的大小无关紧要,您通常可以逐行处理它们。

我唯一的问题是性能,这当然很慢

定义。你觉得什么是慢?如果处理得当,解析它们会非常快。

我想知道是否有办法可以改进这一点,因为我只需要通过我找到的键找到并且 然后停止循环,所以如果条目位于文件的开头 很快就结束了,但如果是在最后,则需要一段时间。

不使用 CSV 文件? 60 多年前,人们为此发明了数据库。

有没有办法改进我的连续解析算法?

你的意思是除了将解析拉到一个单独的线程中,并使用高效的代码(你可能没有 - 没人知道)。

理论上你可以:

  • 在一个线程上读取,具有良好的缓冲区(更少的 IO = 更快)

  • 将字段拆分到线程 2(可选)

  • 使用任务解析字段(每行每个字段一个),以便使用所有处理器。

我目前正在处理一些(大约 10.000 个)文件(遗憾的是,大小为两位数 gigabte)并且...我这样做(必须按特定顺序处理它们)以充分使用我的计算机。

这应该会给你很多 - 说真的,一个 40mb 的文件应该在 0.x 秒 (0.5 - 0.6) 内加载。

仍然是非常低效的。您没有像所有人一样将文件加载到数据库中的任何原因吗? CSV 作为某种传输格式很好,但它作为数据库很糟糕。

【讨论】:

  • 虽然这里的所有改进都是有用的,但值得向 OP 强调的是,在您的特定情况下,这将是很多工作要做,而且您会看到很少的改进 (大约十分之几秒),因为您没有足够的数据或处理需求。
  • 最重要的是,这也是我要说的,这实际上需要一个索引和类似数据库的方法 - 从 CSV 开始这样做并不是正确的方法。跨度>
【解决方案2】:

为什么不将 csv 转换为普通数据库。即使是 sqlexpress 也可以。

【讨论】:

  • 那么 CSV 文件被客户端删除了,他们从 excel 中导出并定期更新,我可以检查文件修改日期并在更新时创建数据库。这是一个解决方案,虽然不是我正在寻找的解决方案,但如果 CSV 变得太慢,我可能会尝试这个:)
【解决方案3】:

当然。

假设您按字母顺序排序。
然后,从中间开始。
每次迭代,移动到顶部或底部的中间;哪个有相应的密钥。

这个算法有 O(log n)。

这被称为“二分搜索”,是“Mike Christianson”在他的评论中所建议的。

【讨论】:

  • 啊!订购的 CSV ......这会起作用,但 CSV 文件没有订购,并且解析文件以订购它可能不值得,因为我可以按照@Lakis 所说的那样做。但我喜欢这个主意:)
【解决方案4】:

建议您将一个 40Mb 的文件分成几个较小的文件。 并且使用Parallel.ForEach 可以提高文件处理性能

【讨论】:

  • 我什至不知道这是可能的,尽管算法本身仍然是 O(n),但由于并行性,这会计算得更快 :) 我会试试这个!谢谢!
  • 如果您的文件位于单个 HD 上,这实际上可能会减慢速度。
【解决方案5】:

您可以将 CSV 加载到 DataTable 中并使用比循环更快的可用操作

将其加载到数据库并对其执行操作是另一种选择

【讨论】:

    【解决方案6】:

    我相信,这是顺序读取 CSV 文件的最快方法。可能还有其他方法可以从 CSV 中提取数据,但如果您仅限于这种方法,那么此解决方案可能适合您。

    const int BUFFER_SIZE = 0x8000;  //represents 32768 bytes
    public unsafe void parseCSV(string filePath)
    {
         byte[] buffer = new byte[BUFFER_SIZE];
         int workingSize = 0; //store how many bytes left in buffer
         int bufferSize = 0; //how many bytes were read by the file stream
         StringBuilder builder = new StringBuilder();
         char cByte; //character representation of byte
         using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
         {
             do
             {
                  bufferSize = fs.Read(buffer, 0, BUFFER_SIZE);
                  workingSize = bufferSize;
                  fixed (byte* bufferPtr = buffer)
                  {
                       byte* workingBufferPtr = bufferptr;
                       while (workingSize-- > 0)
                       {
                            switch (cByte = (char)*workingBufferPtr++)
                            {
                                case '\n':
                                    break;
                                case '\r':
                                case ',':
                                    builder.ToString();
                                    builder.Clear();
                                    break;
                                default:
                                    builder.Append(cByte);
                                    break;
                            }
                       }
                  }
             } while (bufferSize != 0);
         }
    }
    

    解释:

    • 将文件读入字节缓冲区。这将使用基本的Filestream 类来完成,它可以访问始终快速的Read()
    • 不安全的代码。虽然我通常建议不要使用不安全的代码,但在遍历任何类型的缓冲区时,使用指针可以带来加速。
    • StringBuilder 因为我们会将字节连接成可用的字符串来测试密钥。 StringBuilder 是迄今为止将字节追加到一起并从中获取可用字符串的最快方法。

    请注意,此方法与RFC 4180 相当不协调,但如果您处理引号,则可以轻松修改我发布的代码以处理修剪。

    【讨论】:

    • 我不确定 - 说真的。这可能是“明智和愚蠢”类别中最快的,并且您忽略了并非所有字段都可能是字符串的事实(即可能需要额外的转换)。最后,您可能会节省很多 - 非常少(字符串拆分)。我会在 FileStream 上使用 BUfferedStream (大缓冲区,比如半兆字节),使用字符串拆分,然后从那里取出 - 假设大多数字段都被解析并将结果放入一个类;)
    • @TomTom 我在 104MB CSV 上做了一些基本测试。我提出解决方案的方式实际上是表现最慢的(大约 5%)。安全代码(数组索引)和带缓冲流的安全代码(512KB 缓冲区)更快且大致相同。我不确定您所指的字符串拆分是什么,因为它是字符串操作并且仍然需要转换。
    • 正是我所指的。由于大部分时间将花在转换和字符串操作上,因此即使节省 50% 的负载也只会使有效差异为零;)过早的优化。我认为(未检查)没有真正收益的​​不安全代码。很大程度上取决于解析的复杂程度。
    猜你喜欢
    • 1970-01-01
    • 2021-05-29
    • 2020-08-05
    • 1970-01-01
    • 2015-06-02
    • 1970-01-01
    • 2012-06-04
    相关资源
    最近更新 更多