C#:读取巨大的CSV文件

本文关键字:CSV 文件 巨大 读取 | 更新日期: 2023-09-27 18:35:50

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

它现在运行良好,而且很容易解析,我唯一的问题是性能,当然性能相当慢。

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

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

有没有办法改进我的顺序解析算法?

C#:读取巨大的CSV文件

首先:"读取巨大的CSV文件"和"所以我正在解析一个40MB的CSV文件"。我这里有 10+ GB 的空间分隔文件 - 你会怎么称呼这些文件?

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

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

定义。你认为什么是慢的?如果操作得当,解析它们非常快。

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

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

有没有办法改进我的安全解析算法?

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

从理论上讲,您可以:

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

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

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

我目前正在处理一些(大约 10.000 个)文件(可悲的是大小为两位数的千兆字节)和......我以这种方式(必须按特定顺序处理它们)以充分利用我的计算机。

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

这仍然非常低效。您不像所有人那样将文件加载到数据库中的任何原因?CSV作为某些传输格式很好,作为数据库很糟糕。

为什么不将csv转换为普通数据库。即使是sqlexpress也会没问题。

当然。

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

该算法具有 O(log n)。

这被称为"二进制搜索",也是"迈克·克里斯蒂安森"在他的评论中建议的。

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

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

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

我相信,这是按顺序读取 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 相当不同,但如果您处理引号,您可以轻松修改我发布的代码来处理修剪。