在C#中存储/检索字典的最有效方法?

时间:2013-10-16 16:31:49

标签: c# performance file-access

我有一个dictionary<string, int[]>,我需要从磁盘中尽可能有效地存储和检索。

密钥长度(字符串)通常在1到60个字符(unicode)之间变化,但可能超过该长度(但这是边缘的,可以丢弃这些值)。数组中的整数将在1到1亿之间。 (通常为1至5M)

我的第一个想法是使用分隔格式:

key [tab] int,int,int,int,...
key2 [tab] int,int,int,int,...
...

并按如下方式加载字典:

string[] Lines = File.ReadAllLines(sIndexName).ToArray();
string[] keyValues = new string[2];
List<string> lstInts =  new List<string>();
// Skip the header line of the index file.
for (int i = 1; i < Lines.Length; i++)
{
    lstInts.Clear();
    keyValues = Lines[i].Split('\t');
    if (keyValues[1].Contains(','))
    {
        lstInts.AddRange(keyValues[1].Split(','));
    }
    else
    {
        lstInts.Add(keyValues[1]);
    }
    int[] iInts = lstInts.Select(x => int.Parse(x)).ToArray();
    Array.Sort(iInts);
    dic.Add(keyValues[0], iInts);               
}

虽然有效但是超出了潜在的尺寸要求,显然这种方法永远不会很好地扩展。

是否有解决此问题的现成解决方案,还是需要完全重写算法?


编辑:我有点尴尬承认它,但我不知道字典可以被序列化为二进制。我给了它一个测试运行,这几乎是我需要的。

以下是代码(建议欢迎)

    public static void saveToFile(Dictionary<string, List<int>> dic)
{
    using (FileStream fs = new FileStream(_PATH_TO_BIN, FileMode.OpenOrCreate))
    {
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(fs, dic);
    }
}

public static Dictionary<string, List<int>> loadBinFile()
{
    FileStream fs = null;
    try
    {
        fs = new FileStream(_PATH_TO_BIN, FileMode.Open);
        BinaryFormatter bf = new BinaryFormatter();
        return (Dictionary<string, List<int>>)bf.Deserialize(fs);
    }
    catch
    {
        return null;
    }
}

使用100k条目的字典,每个条目具有4k个整数数组,序列化需要14秒,反序列化需要10秒,结果文件为1.6gb。

@Patryk:请将您的评论转换为答案,以便我将其标记为已批准。

2 个答案:

答案 0 :(得分:1)

我猜你想在加载过程中减少内存占用。现在,您将所有内容加载到数组中的内存中,然后将所有内容复制到字典中。在原始阵列超出范围并进行垃圾收集之前,将会有一段时间,大约需要2倍的内存使用量。如果它是一个非常大的文件,那么这可能会很多......如果它只有几兆字节,那不是什么大不了的事。

如果您想更有效地执行此操作,您可以从流中读取数据,如下所示:

string fileName = @"C:\...";
var dict = new Dictionary<string, int[]>();

using (var fs = new FileStream(fileName, FileMode.Open))
using (var reader = new StreamReader(fs))
{
    string line;
    while ((line = reader.ReadLine()) != null)
    {
        var values = line.Split(',');
        dict.Add(values[0], values.Skip(1).Select(x => Convert.ToInt32(x)).ToArray());
    }       
}

或者您可以使用Jim建议的快捷方式:

string fileName = @"C:\...";
var dict = new Dictionary<string, int[]>();

foreach (string line in File.ReadLines(fileName))
{
    var values = line.Split(',');
    dict.Add(values[0], values.Skip(1).Select(x => Convert.ToInt32(x)).ToArray());
}

这对文件格式做了一些严格的推定。值得注意的是,每一行的格式为key,int1,int2,int3,int4,...,并且该键不包含逗号。每一行也必须以Environment.NewLine字符结尾。

虽然值得注意的是,您应该考虑这样一个事实:虽然您当前的代码不是非常有效,但它不是您的主要瓶颈。文件读取速度通常是最大的瓶颈。如果您实际遇到代码性能问题,则很可能只是与您同步读取文件有关。任何文件I / O都应该在具有用户界面的应用程序中异步完成。

答案 1 :(得分:1)

Dictionary<TKey, TValue>被标记为[Serializable](并实施ISerializablecan be seen here

这意味着你可以使用例如BinaryFormatter对流执行二进制序列化和反序列化。说,FileStream。 :)