2016-08-24 162 views
2

我正在创建一个分析文件数据质量的工具。所以我需要阅读文件的每一行并分析其中的每一行。我还需要在内存中存储我的文件的所有行,因为用户将能够深入到特定的部分。所以基本上所有的工作都适用于包含数千行的文件。但是,当尝试使用包含超过4百万行的CSV文件时,我会遇到内存不足异常。我认为C#能够处理其内存缓存中的数百万数据,但看起来并不像它。所以我有点卡住,不知道该怎么做。也许我的一段代码不是最高性能的,所以如果你能告诉我一种改进它的方法,那将会很棒吗?只是要记住,我需要在内存中的文件的所有行,因为根据用户的行动,我需要访问特定的行来显示给用户。当读大文件时内存不足

下面是读取每一行

using (FileStream fs = File.Open(this.dlgInput.FileName.ToString(), FileMode.Open, FileAccess.Read, FileShare.Read)) 
using (BufferedStream bs = new BufferedStream(fs)) 
using (System.IO.StreamReader sr = new StreamReader(this.dlgInput.FileName.ToString(), Encoding.Default, false, 8192)) 
{ 
    string line; 
    if (this.chkSkipHeader.Checked) 
    { 
     sr.ReadLine(); 
    } 

    progressBar1.Visible = true; 
    int nbOfLines = File.ReadLines(this.dlgInput.FileName.ToString()).Count(); 
    progressBar1.Maximum = nbOfLines; 

    this.lines = new string[nbOfLines][]; 
    this.patternedLines = new string[nbOfLines][]; 
    for (int i = 0; i < nbOfLines; i++) 
    { 
     this.lines[i] = new string[this.dgvFields.Rows.Count]; 
     this.patternedLines[i] = new string[this.dgvFields.Rows.Count]; 
    } 

    // Read and display lines from the file until the end of 
    // the file is reached. 
    while ((line = sr.ReadLine()) != null) 
    { 
     this.recordCount += 1; 
     char[] c = new char[1] { ',' }; 
     System.Text.RegularExpressions.Regex CSVParser = new System.Text.RegularExpressions.Regex(",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))"); 
     String[] fields = CSVParser.Split(line); 
     ParseLine(fields); 
     this.lines[recordCount - 1] = fields; 
     progressBar1.PerformStep(); 
    } 
} 

并且在下面的ParseLine功能也通过阵列一些分析需要保持在存储器中的呼叫:

private void ParseLine(String[] fields2) 
{ 
    for (int j = 0; j <= fields2.Length - 1; j++) 
    { 
     if ((int)this.dgvFields.Rows[j].Cells["colSelected"].Value == 1) 
     { 
      /*' ************************************************ 
      ' Save Number of Counts by Value 
      ' ************************************************/ 

      if (this.values[j].ContainsKey(fields2[j])) 
      { 
       //values[0] = Dictionary<"TEST", 1> (fields2[0 which is source code] = count]) 
       this.values[j][fields2[j]] += 1; 
      } 
      else 
      { 
       this.values[j].Add(fields2[j], 1); 
      } 

      /* ' ************************************************ 
      ' Save Pattern Values/Counts 
      ' ************************************************/ 

      string tmp = System.Text.RegularExpressions.Regex.Replace(fields2[j], "\\p{Lu}", "X"); 
      tmp = System.Text.RegularExpressions.Regex.Replace(tmp, "\\p{Ll}", "x"); 
      tmp = System.Text.RegularExpressions.Regex.Replace(tmp, "[0-9]", "0"); 


      if (this.patterns[j].ContainsKey(tmp)) 
      { 
       this.patterns[j][tmp] += 1; 
      } 
      else 
      { 
       this.patterns[j].Add(tmp, 1); 
      } 

      this.patternedLines[this.recordCount - 1][j] = tmp; 
      /* ' ************************************************ 
      ' Count Blanks/Alpha/Numeric/Phone/Other 
      ' ************************************************/ 


      if (String.IsNullOrWhiteSpace(fields2[j])) 
      { 
       this.blanks[j] += 1; 
      } 
      else if (System.Text.RegularExpressions.Regex.IsMatch(fields2[j], "^[0-9]+$")) 
      { 
       this.numeric[j] += 1; 
      } 
      else if (System.Text.RegularExpressions.Regex.IsMatch(fields2[j].ToUpper().Replace("EXTENSION", "").Replace("EXT", "").Replace("X", ""), "^[0-9()\\- ]+$")) 
      { 
       this.phone[j] += 1; 
      } 
      else if (System.Text.RegularExpressions.Regex.IsMatch(fields2[j], "^[a-zA-Z ]+$")) 
      { 
       this.alpha[j] += 1; 
      } 
      else 
      { 
       this.other[j] += 1; 
      } 

      if (this.recordCount == 1) 
      { 
       this.high[j] = fields2[j]; 
       this.low[j] = fields2[j]; 
      } 
      else 
      { 
       if (fields2[j].CompareTo(this.high[j]) > 0) 
       { 
        this.high[j] = fields2[j]; 
       } 

       if (fields2[j].CompareTo(this.low[j]) < 0) 
       { 
        this.low[j] = fields2[j]; 
       } 
      } 
     } 
    } 
} 

更新:新的代码

int nbOfLines = File.ReadLines(this.dlgInput.FileName.ToString()).Count(); 
     //Read file 

     using (System.IO.StreamReader sr = new StreamReader(this.dlgInput.FileName.ToString(), Encoding.Default, false, 8192)) 
     { 
      string line; 
      if (this.chkSkipHeader.Checked) 
      { sr.ReadLine(); } 
      progressBar1.Visible = true; 

      progressBar1.Maximum = nbOfLines; 
      this.lines = new string[nbOfLines][]; 
      this.patternedLines = new string[nbOfLines][]; 
      for (int i = 0; i < nbOfLines; i++) 
      { 
       this.lines[i] = new string[this.dgvFields.Rows.Count]; 
       this.patternedLines[i] = new string[this.dgvFields.Rows.Count]; 
      } 

      // Read and display lines from the file until the end of 
      // the file is reached. 
      while ((line = sr.ReadLine()) != null) 
      { 
       this.recordCount += 1; 
       char[] c = new char[1] { ',' }; 
       System.Text.RegularExpressions.Regex CSVParser = new System.Text.RegularExpressions.Regex(",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))"); 
       String[] fields = CSVParser.Split(line); 
       ParseLine(fields); 
       this.lines[recordCount - 1] = fields; 
       progressBar1.PerformStep(); 
      } 
     } 
+2

请正确格式化您的代码 – byxor

+7

c#无法从无到有创建内存。如果你的数据比适合你的系统内存和/或虚拟内存的数据多,那么你就会陷入困境。要么改变代码的工作方式以减少内存负载,要么获得更多的内存。 –

+1

我有4个内核和16GB的内存 - 对于4百万行文件来说不够吗? –

回答

-1

如果您需要处理大量数据,请考虑使用da tabases。它们是为了这种目的而设计的。您也可以通过特定请求查询它们。可能一个关键价值商店已经足够。看看https://ravendb.net/https://www.mongodb.com/

+1

出于好奇为什么downvotes?这似乎是合理的。 – EJoshuaS

+0

谢谢@EJoshuaS。如果您不满意,请随时添加评论。否则,很难提高答案。 –

-1

即使您需要用户对所有数据执行操作(类似于baretail),您也不应该在memeory中包含所有行,您必须从磁盘读取行,并且仅用于窗口,当用户看到更多数据时,用户可以看到更多的数据,然后用相同的窗口宽度从磁盘上流出更多的数据,但从来没有用过所有行,想象的文件就像40 GB ......全部都不实际的他们加载。 Here是如何做到这一点的例子,从其他承包商,客人的要求,这里是答案的代码中提到,信贷@James King

// This really needs to be a member-level variable; 
private static readonly object fsLock = new object(); 

// Instantiate this in a static constructor or initialize() method 
private static FileStream fs = new FileStream("myFile.txt", FileMode.Open); 


public string ReadFile(int fileOffset) { 

    byte[] buffer = new byte[bufferSize]; 

    int arrayOffset = 0; 

    lock (fsLock) { 
     fs.Seek(fileOffset, SeekOrigin.Begin); 

     int numBytesRead = fs.Read(bytes, arrayOffset , bufferSize); 

     // Typically used if you're in a loop, reading blocks at a time 
     arrayOffset += numBytesRead; 
    } 

    // Do what you want to the byte array and return it 

} 
+0

至少从你连接的例子中复制相关部分到 –

+0

1)我想给那个写这个答案的人以功劳,2)这是一个SO内的链接,所以不需要公开发表,不用担心这个链接将会死亡。 – Hasson

+1

1)你可以在你的答案中给予作者功劳,2)答案和问题会在SO上被删除。 – Tim

0

C#有大单的对象怎么能限制(因此例外)。考虑一下这样一个事实,即使数组中的每个字符串都是1个字节,400万字节仍然会在4千兆字节左右,据我所知,.NET中单个对象的默认最大大小为2千兆字节。无论您的整个系统有多少内存,情况都是如此。

有可用的堆栈溢出如何创建大阵一对夫妇的文章:I need very big array length(size) in C#OutOfMemoryException on declaration of Large Array

据我了解,这是部分的.NET框架如何管理从32位过渡的结果64位。 (请注意,2千兆字节大致对应于32位带符号整数的最大值)。在更新版本的.NET中(按照我读过的4.5之后,但我从未尝试过),我想你可以在一定程度上更改最大对象大小。还有一些可以使用的特殊类(例如自定义BigArray类)来解决空间限制。

请记住,数组要求能够分配连续的内存地址(这就是为什么您可以通过索引进行常量访问 - 地址是指向第一个指针的常量偏移量项,所以框架可以通过将索引乘以32或其他常数来计算内存位置,这取决于内存大小并将其添加到指向第一项的指针中的地址以找出项目的位置)。因此,内存中的碎片可能会减少可用于阵列的有效内存量。

+0

小心解释downvote? – EJoshuaS

0

您需要创建助手类,它将缓存整个文件中每行的起始位置。

int[] cacheLineStartPos; 

public string GetLine (int lineNumber) 
{ 
    int linePositionInFile = cacheLineStartPos[lineNumber]; 

    reader.Position = linePositionInFile; 

    return reader.ReadLine(); 
} 

当然这只是一个例子,而逻辑可能更复杂。

+0

抱歉,这是为了什么?我不确定要明白的目的 –

+0

@Mélanie:使用这种方法,你将避免错误的代码,如:this.lines = new string [nbOfLines] []; this.patternedLines = new string [nbOfLines] [];这消耗了大量的内存。 – apocalypse

相关问题