5

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

4

2 回答 2

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 都应该在具有用户界面的应用程序中异步完成。

于 2013-10-16T16:50:31.487 回答
1

Dictionary<TKey, TValue>标记为[Serializable](和实现ISerializable),可以在这里看到

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

于 2013-10-18T08:20:18.840 回答