C#字典和高效的内存使用

时间:2010-01-29 20:14:00

标签: c# dictionary memory-management

我有一个工具来比较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个字典一样快。

非常感谢任何投入。

3 个答案:

答案 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。

其次,你能在读完所有输入之前开始写输出吗?我认为这很棘手,因为看起来你需要在写出之前对所有输出项进行排序,但可能是你可以看到的东西。