[英]C# Dictionary and Efficient Memory Usage
我有一個工具來比較2個csv文件,然后將每個單元格裝入6個桶中的一個。 基本上,它讀取csv文件(使用快速csv閱讀器,信用: http : //www.codeproject.com/KB/database/CsvReader.aspx ),然后根據提供的密鑰創建一個與每個文件有關的字典。用戶。 然后我遍歷比較值並寫入結果csv文件的字典。
雖然速度非常快,但在內存使用方面效率非常低。 我無法比較我的盒子上超過150 MB的文件和3 GB的物理內存。
這是一個用於讀取預期文件的代碼段。 在這篇文章的最后,任務管理器的內存使用量接近500 MB。
// Read Expected
long rowNumExp;
System.IO.StreamReader readerStreamExp = new System.IO.StreamReader(@expFile);
SortedDictionary<string, string[]> dictExp = new SortedDictionary<string, string[]>();
List<string[]> listDupExp = new List<string[]>();
using (CsvReader readerCSVExp = new CsvReader(readerStreamExp, hasHeaders, 4096))
{
readerCSVExp.SkipEmptyLines = false;
readerCSVExp.DefaultParseErrorAction = ParseErrorAction.ThrowException;
readerCSVExp.MissingFieldAction = MissingFieldAction.ParseError;
fieldCountExp = readerCSVExp.FieldCount;
string keyExp;
string[] rowExp = null;
while (readerCSVExp.ReadNextRecord())
{
if (hasHeaders == true)
{
rowNumExp = readerCSVExp.CurrentRecordIndex + 2;
}
else
{
rowNumExp = readerCSVExp.CurrentRecordIndex + 1;
}
try
{
rowExp = new string[fieldCount + 1];
}
catch (Exception exExpOutOfMemory)
{
MessageBox.Show(exExpOutOfMemory.Message);
Environment.Exit(1);
}
keyExp = readerCSVExp[keyColumns[0] - 1];
for (int i = 1; i < keyColumns.Length; i++)
{
keyExp = keyExp + "|" + readerCSVExp[i - 1];
}
try
{
readerCSVExp.CopyCurrentRecordTo(rowExp);
}
catch (Exception exExpCSVOutOfMemory)
{
MessageBox.Show(exExpCSVOutOfMemory.Message);
Environment.Exit(1);
}
try
{
rowExp[fieldCount] = rowNumExp.ToString();
}
catch (Exception exExpRowNumOutOfMemory)
{
MessageBox.Show(exExpRowNumOutOfMemory.Message);
Environment.Exit(1);
}
// Dedup Expected
if (!(dictExp.ContainsKey(keyExp)))
{
dictExp.Add(keyExp, rowExp);
}
else
{
listDupExp.Add(rowExp);
}
}
logFile.WriteLine("Done Reading Expected File at " + DateTime.Now);
Console.WriteLine("Done Reading Expected File at " + DateTime.Now + "\r\n");
logFile.WriteLine("Done Creating Expected Dictionary at " + DateTime.Now);
logFile.WriteLine("Done Identifying Expected Duplicates at " + DateTime.Now + "\r\n");
}
有什么東西,我能做些什么來提高內存效率? 我能做些什么不同的東西,消耗更少的mermory?
歡迎任何想法。
謝謝大家的反饋。
我已按照建議的方式合並了更改,以便在字典中存儲行的索引而不是行本身。
這是與新實現相同的代碼片段。
// Read Expected
long rowNumExp;
SortedDictionary<string, long> dictExp = new SortedDictionary<string, long>();
System.Text.StringBuilder keyExp = new System.Text.StringBuilder();
while (readerCSVExp.ReadNextRecord())
{
if (hasHeaders == true)
{
rowNumExp = readerCSVExp.CurrentRecordIndex + 2;
}
else
{
rowNumExp = readerCSVExp.CurrentRecordIndex + 1;
}
for (int i = 0; i < keyColumns.Length - 1; i++)
{
keyExp.Append(readerCSVExp[keyColumns[i] - 1]);
keyExp.Append("|");
}
keyExp.Append(readerCSVExp[keyColumns[keyColumns.Length - 1] - 1]);
// Dedup Expected
if (!(dictExp.ContainsKey(keyExp.ToString())))
{
dictExp.Add(keyExp.ToString(), rowNumExp);
}
else
{
// Process Expected Duplicates
string dupExp;
for (int i = 0; i < fieldCount; i++)
{
if (i >= fieldCountExp)
{
dupExp = null;
}
else
{
dupExp = readerCSVExp[i];
}
foreach (int keyColumn in keyColumns)
{
if (i == keyColumn - 1)
{
resultCell = "duplicateEXP: '" + dupExp + "'";
resultCell = CreateCSVField(resultCell);
resultsFile.Write(resultCell);
comSumCol = comSumCol + 1;
countDuplicateExp = countDuplicateExp + 1;
}
else
{
if (checkPTColumns(i + 1, passthroughColumns) == false)
{
resultCell = "'" + dupExp + "'";
resultCell = CreateCSVField(resultCell);
resultsFile.Write(resultCell);
countDuplicateExp = countDuplicateExp + 1;
}
else
{
resultCell = "PASSTHROUGH duplicateEXP: '" + dupExp + "'";
resultCell = CreateCSVField(resultCell);
resultsFile.Write(resultCell);
}
comSumCol = comSumCol + 1;
}
}
if (comSumCol <= fieldCount)
{
resultsFile.Write(csComma);
}
}
if (comSumCol == fieldCount + 1)
{
resultsFile.Write(csComma + rowNumExp);
comSumCol = comSumCol + 1;
}
if (comSumCol == fieldCount + 2)
{
resultsFile.Write(csComma);
comSumCol = comSumCol + 1;
}
if (comSumCol > fieldCount + 2)
{
comSumRow = comSumRow + 1;
resultsFile.Write(csCrLf);
comSumCol = 1;
}
}
keyExp.Clear();
}
logFile.WriteLine("Done Reading Expected File at " + DateTime.Now + "\r\n");
Console.WriteLine("Done Reading Expected File at " + DateTime.Now + "\r\n");
logFile.WriteLine("Done Analyzing Expected Duplicates at " + DateTime.Now + "\r\n");
Console.WriteLine("Done Analyzing Expected Duplicates at " + DateTime.Now + "\r\n");
logFile.Flush();
但是,問題是我需要內存中的數據集。 我實際上遍歷兩個字典,根據鍵查找匹配,不匹配,重復和丟失。
使用這種存儲行索引的方法,我仍然使用大量內存,因為對於動態訪問,我現在必須使用csv閱讀器的緩存版本。 因此,盡管字典現在要小得多,但數據的緩存彌補了節省的成本,我仍然得到了類似的內存使用量。
希望,我有意義...... :)
一種選擇是完全擺脫字典並只循環瀏覽2個文件,但不確定性能是否與比較2個字典一樣快。
任何投入都非常感謝。
您可以用StringBuilder替換keyExp
。 在這樣的循環中重新分配字符串將繼續分配更多的內存,因為字符串是不可變的。
StringBuilder keyExp = new StringBuilder();
...
keyExp.Append("|" + readerCSVExp[i - 1]) ;
...
很多字符串是一樣的嗎? 您可以嘗試實習它們 ,然后任何相同的字符串將共享相同的內存而不是副本...
rowExp[fieldCount] = String.Intern(rowNumExp.ToString());
// Dedup Expected
string internedKey = (String.Intern(keyExp.ToString()));
if (!(dictExp.ContainsKey(internedKey)))
{
dictExp.Add(internedKey, rowExp);
}
else
{
listDupExp.Add(rowExp);
}
我不確定代碼是如何工作的,但除此之外我不會說你不需要將rowExp
保留在字典中,保留其他東西,比如數字並將rowExp
寫回另一個文件中的磁盤。 這可能會節省你最多的內存,因為這似乎是文件中的一個字符串數組,所以可能很大。 如果您將其寫入文件並將其保存在文件中,那么如果您需要處理,將來可以再次返回該文件。 如果您將文件中的偏移量保存為字典中的值,則可以快速找到它。 也許 :)。
如果我弄錯了,請告訴我。
上面的代碼讀取一個CSV文件並查找重復的密鑰。 每行進入兩組中的一組,一組用於重復鍵,一組用於沒有。
你如何處理這些行集?
他們寫的是不同的文件嗎?
如果是這樣,沒有理由將非unqiue行存儲在列表中,因為您發現它們將它們寫入文件。
當你找到重復項時,不需要存儲整行,只需存儲密鑰,並將行寫入文件(如果你想讓它們保持獨立,顯然是一個不同的文件)。
如果需要對不同的集合進行進一步處理,則不存儲整行,而不是存儲行號。 然后,當你對行進行任何操作時,你需要行號才能再次獲取行。
注意:您可以將偏移量存儲在行的起始點的文件中,而不是存儲行號。 然后,如果需要,您可以隨機訪問該文件並讀取行。
只要對你可能有的任何問題(或澄清)評論這個答案,我會更新答案,無論如何我會在這里待幾個小時。
編輯
您可以通過不存儲鍵來進一步減少內存占用,而是存儲鍵的哈希值。 如果您發現重復,請在文件中尋找該位置,重新讀取該行並比較實際的鍵。
如果您還沒有像DotTrace那樣獲得一個探查器來查看哪些對象正在使用內存,那么這將讓您對需要優化的內容有所了解。
查看代碼的一些想法:
你需要存儲listDupExp嗎? 在我看來,列表中你有效地將兩個文件加載到內存中,因此2 x 150MB +一些開銷在任務管理器中很容易接近500MB。
其次,你可以在閱讀所有輸入之前開始編寫輸出嗎? 我認為這很棘手,因為看起來你需要在寫出之前對所有輸出項進行排序,但可能是你可以看到的東西。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.