我有一个工具来比较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个字典一样快。
非常感谢任何投入。
答案 0 :(得分:7)
您可以用StringBuilder替换keyExp
。在这样的循环中重新分配字符串将继续分配更多的内存,因为字符串是不可变的。
StringBuilder keyExp = new StringBuilder();
...
keyExp.Append("|" + readerCSVExp[i - 1]) ;
...
很多字符串都一样吗?您可以尝试interning them,然后任何相同的字符串将共享相同的内存而不是副本...
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
,保留其他东西,比如数字并写{{ 1}}在另一个文件中退回到磁盘。这可能会节省你最多的内存,因为这似乎是文件中的一个字符串数组,所以可能很大。如果您将其写入文件并将其保存在文件中,那么如果您需要处理,将来可以再次返回该文件。如果您将文件中的偏移量保存为字典中的值,则可以快速找到它。也许:)。
答案 1 :(得分:3)
告诉我是否有任何错误。
上面的代码读取一个CSV文件并查找重复的密钥。每行进入两组中的一组,一组用于重复键,一组用于没有。
你如何处理这些行集?
它们是否写入不同的文件?
如果是这样,没有理由将非unqiue行存储在列表中,因为您发现它们将它们写入文件。
当你找到重复项时,不需要存储整行,只需存储密钥,然后将行写入文件(如果你想保持它们分开,显然是一个不同的文件)。
如果您需要对不同的集合进行进一步处理,则不存储整行,而不是存储行号。然后,当你对行进行任何操作时,你需要行号才能再次获取行。
注意:您可以将偏移量存储在行的起始点的文件中,而不是存储行号。然后,如果需要,您可以随机访问该文件并读取行。
只要对你可能有的任何问题(或澄清)评论这个答案,我会更新答案,无论如何我会在这里待几个小时。
修改强>
您可以通过不存储键来进一步减少内存占用,而是存储键的哈希值。如果您发现重复,请在文件中寻找该位置,重新读取该行并比较实际的密钥。
答案 2 :(得分:2)
如果您还没有像DotTrace那样获得一个分析器来查看哪些对象正在使用内存,那么这将让您对需要优化的内容有所了解。
查看代码的一些想法:
您需要存储listDupExp吗?在我看来,列表中你有效地将两个文件加载到内存中,因此在任务管理器中2 x 150MB +一些开销很容易接近500MB。
其次,你能在读完所有输入之前开始写输出吗?我认为这很棘手,因为看起来你需要在写出之前对所有输出项进行排序,但可能是你可以看到的东西。