2016-08-24 102 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

相關問題