【问题标题】:Most efficient way to store/retrieve a dictionary in C#?在 C# 中存储/检索字典的最有效方法?
【发布时间】:2013-10-16 16:31:49
【问题描述】:

我有一个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:请将您的评论转换为答案,以便我将其标记为已批准。

【问题讨论】:

  • “有效”是指“尺寸有效”?
  • @Stefan - 尺寸/速度似乎不是问题,因为 OP 将其存储在文本文件中......但确实有必要知道需要什么类型的“规模足够好”才可以得到答复。
  • 一些旁注;而不是让您的列表在循环之外并不断清除它,只需在循环内定义列表即可。拆分不带分隔符的字符串只会返回一个大小为 1 且具有该值的数组,因此您无需检查字符串是否包含 ,,只需每次拆分它并将所有值添加到列表中,即使“全部”只是一个。你需要对数组进行排序吗?如果您要重新创建现有结构,为什么它们还没有排序?
  • @AlexeiLevenkov (非常有效的)问题是这里优化了什么,程序的速度、文件的大小、代码的可读性等等?
  • 注意:Dictionary 被标记为[Serializable],因此您可以使用BinaryFormatter 轻松地将其序列化/反序列化到二进制文件。我不知道它是否适合您的用例...

标签: c# performance file-access


【解决方案1】:

Dictionary&lt;TKey, TValue&gt; 被标记为[Serializable](并实现ISerializable)其中can be seen here。

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

【讨论】:

    【解决方案2】:

    我猜你想减少加载过程中的内存占用。现在您将所有内容加载到数组中的内存中,然后将所有内容复制到字典中。在原始数组超出范围并被垃圾收集之前,将有一段时间需要大约 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。请注意,您可以用foreach (string line in File.ReadLines(fileName)) 替换很多代码
    猜你喜欢
    • 1970-01-01
    • 2011-02-17
    • 1970-01-01
    • 2013-06-16
    • 2014-05-14
    • 2017-01-06
    • 1970-01-01
    • 2020-04-09
    • 2016-07-25
    相关资源
    最近更新 更多