C#:读取巨大的CSV文件

时间:2012-05-30 15:37:01

标签: c# csv

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

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

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

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

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

6 个答案:

答案 0 :(得分:4)

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

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

  

我唯一的问题是表现,这当然是相当慢的

定义。你觉得什么慢?如果做得好,解析它们的速度非常快。

  

我想知道我是否有办法改善这一点,因为我只需要通过钥匙找到我找到的   然后停止循环,所以如果条目是在文件的初始化它   很快完成,但如果它结束需要一段时间。

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

  

有没有办法可以改进我的secuential解析算法?

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

理论上你可以:

  • 在一个线程上读取,使用一个不错的缓冲区(减去IO =更快)

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

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

我目前正在处理一些(大约10.000个)文件(大概是两位数的gigabte)并且......我这样做(必须按特定顺序处理它们)才能完全使用我的电脑。

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

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

答案 1 :(得分:3)

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

答案 2 :(得分:3)

当然。

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

该算法具有O(log n )。

这被称为“二元搜索”,正是“Mike Christianson”在评论中所暗示的。

答案 3 :(得分:1)

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

答案 4 :(得分:0)

您可以将CSV加载到DataTable中,并使用可能比循环

更快的可用操作

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

答案 5 :(得分:0)

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