2013-11-26 100 views
0

我想根据每个记录中第一项的关键字将大文件(逗号分隔,每个项用双引号包裹)分割成许多较小的文件,通常是相同的密钥超过1条记录。将一个文件分割成多个小文件的优化

这个大文件的范围可以从1GB到2GB,生成的文件数量可以在10,000-30,000范围内,每个文件夹在以该密钥命名的子文件夹中。我想在每行上做一个StreamReader.ReadLine(),将结果连接起来,直到它到达一个不同的键(用信号表示上一个键的最后一个数据),然后调用一个函数来写入这些文件asyncronously。我调用Windows排序来排序这些文件以使这些键连续(因此我只需打开一次文件),但操作完成仍需要20分钟。有什么办法可以加快速度吗?

sfd = new SaveFileDataDelegate(this.SaveFileData); 



private void CSVParse(string filename, string unzippedFilePath, string feedname) 
{ 
    StreamReader filestream = null; 
    FileStream readerStream = null; 
    try 
    { 
     readerStream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.None, 120000, FileOptions.SequentialScan); 
     filestream = new StreamReader(readerStream, Encoding.UTF8, false, 120000); 

     string tempstring = ""; 
     string buffer = ""; 
     string lastlotkey = ""; 
     IAsyncResult result = null; 

     activityLog.Log("Parsing File: " + filename); 

     while (((tempstring = filestream.ReadLine()) != null) || buffer != "") 
     { 
      if (tempstring == null) 
      { 
       tempstring = ""; 
      } 
      string lotkey = tempstring.Replace("\"","").Split(',').First(); 
      if (lotkey == tempstring && tempstring != "") 
      { 
       break; 
      } 
      if (lotkey == "DealerID") 
      { 
       continue; 
      } 
      if (lastlotkey == "") 
      { 
       lastlotkey = lotkey; 
      } 
      if ((lotkey != lastlotkey && buffer.Length > 0)) 
      { 
       result = sfd.BeginInvoke(outputDirectory + @"\" + feedname + @"\" + lastlotkey + @"\" + (filename.Split('\\').Last()).Split('.').First() + ".txt", buffer, outputDirectory + @"\" + feedname + @"\" + lastlotkey,null,null); 
       lastlotkey = lotkey; 
       buffer = ""; 

       if (tempstring == "") 
       { 
        continue; 
       } 
      } 
      if (buffer.Length > 0) 
      { 
       buffer = buffer + "\r\n"; 
      } 
      buffer = buffer + tempstring; 
     } 
     filestream.Close(); 
     readerStream.Close(); 
     if (result != null) 
     { 
      result.AsyncWaitHandle.WaitOne(-1); 
     } 
     return; 
    } 

    catch (Exception e) 
    { 
     activityLog.Log("Error Occurred: " + e.ToString()); 
     if (filestream != null) 
     { 
      filestream.Close(); 
     } 
     hadError = true; 
     return; 
    } 
} 


private void SaveFileData(string file, string buffer, string directory) 
{ 
    // create file from last lot key with data from parsing, write, close, update lastlotkey 
    Directory.CreateDirectory(directory); 
    FileStream fs = null; 
    StreamWriter temp = null; 
    try 
    { 
     if (!File.Exists(file)) 
     { 
      fs = new FileStream(file, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, 120000); 
     } 
     else 
     { 
      fs = new FileStream(file, FileMode.Truncate, FileAccess.Write, FileShare.None, 120000); 
     } 
     temp = new StreamWriter(fs, Encoding.UTF8, 120000); 
     temp.AutoFlush = false; 
     temp.WriteLine(headerLine); 
     temp.Write(buffer); 
     temp.Flush(); 
     temp.Close(); 
     fs.Close(); 
    } 
    catch (Exception e) 
    { 
     activityLog.Log("Error Occurred: " + e.ToString()); 
     if (fs != null) 
     { 
      fs.Close(); 
     } 
     if (temp != null) 
     { 
      temp.Close(); 
     } 
     hadError = true; 
     return; 
    } 
} 

编辑

我爬到堆栈溢出和互联网最深的肠子,用线仿形切削线后,我发现字符串连接实际上是解析程序的繁重(后文件复制和Windows排序),用Stringbuilder替代了这一改进,总处理时间从20分钟(副本+排序+解析)下降到5分钟的副本+排序和2分钟的解析,总共7分钟。速度提高130%

+0

StringBuilder再次触击。我想知道是否浪费时间来尝试调试使用String写入不当的程序时,String通过String interning已经超过了保存的全部内存。 –

回答

0

如果删除在硬盘上写入的代码,速度有多快? 许多减速将是由于硬盘驱动器。得到一个ssd:P

由于你循环了很多,我会限制循环内的代码。删除重复的代码并在循环外获得尽可能多的代码。

第一个if不需要sicne你已经在while检查null了。

如果你有很多类似的线条,那么分割可能并不需要所有的时间。您可以改用.StartsWith。

如果文件一致,则不需要删除“。您可以比较”。

由于您正在检查第二个空的临时字符串,如果。也许你想在分割之前这么做,因为分割一个空字符串是没有用的。

很多用于获取新文件名的字符串操作都可以在循环之外完成。

+0

这似乎有所帮助,我的处理时间似乎提高了大约5%,我仍然得到了我的瓶颈是磁盘IO相关的偷偷摸摸的怀疑,你有关于打开/写入文件,缓冲区大小等逻辑的任何建议? – WestonM

+0

@WestonM使IO更快的唯一方法是限制读/写次数并限制针的移动(例如:读/写许多不同的文件将使针移动的不仅仅是写或读文件)。或者升级到更快的硬件。 –

相关问题