【问题标题】:XML Reading to Dictionary becomes Slower with TimeXML 读取到字典变得越来越慢
【发布时间】:2012-03-27 13:59:54
【问题描述】:

我的 C# 应用程序读取以下结构的 XML 文件。 150mb 的文件里面大约有 250,000 字。

<word>
     <name>kick</name>
     <id>485</id>
     <rels>12:4;4256:3;754:3;1452:2;86:2;125:2;</rels>
</word>

我想将 XML 文件读入字典。这些是我阅读班的一些班员。

private XmlReader Reader;

public string CurrentWordName;
public int CurrentWordId;
public Dictionary<KeyValuePair<int, int>, int> CurrentRelations;

这是我阅读课的主要方法。它只是从文件中读取下一个单词并获取nameid,并将关系存储到字典中。

CurrentWordId = -1;
CurrentWordName = "";
CurrentRelations = new Dictionary<KeyValuePair<int, int>, int>();

while(Reader.Read())
    if(Reader.NodeType == XmlNodeType.Element & Reader.Name == "word")
    {
        while (Reader.Read())
            if (Reader.NodeType == XmlNodeType.Element & Reader.Name == "name")
            {
                XElement Title = XElement.ReadFrom(Reader) as XElement;
                CurrentWordName = Title.Value;
                break;
            }
        while (Reader.Read())
            if (Reader.NodeType == XmlNodeType.Element & Reader.Name == "id")
            {
                XElement Identifier = XElement.ReadFrom(Reader) as XElement;
                CurrentWordId = Convert.ToInt32(Identifier.Value);
                break;
            }
        while(Reader.Read())
            if (Reader.NodeType == XmlNodeType.Element & Reader.Name == "rels")
            {
                XElement Text = XElement.ReadFrom(Reader) as XElement;
                string[] RelationStrings = Text.Value.Split(';');
                foreach (string RelationString in RelationStrings)
                {
                    string[] RelationsStringSplit = RelationString.Split(':');
                    if (RelationsStringSplit.Length == 2)
                        CurrentRelations.Add(new KeyValuePair<int,int>(CurrentWordId,Convert.ToInt32(RelationsStringSplit[0])), Convert.ToInt32(RelationsStringSplit[1]));
                }
                break;
            }
        break;
    }

if (CurrentRelations.Count < 1 || CurrentWordId == -1 || CurrentWordName == "")
     return false;
else
     return true;

我的 Windows 窗体有一个 backgroundWorker 来读取所有单词。

private void bgReader_DoWork(object sender, DoWorkEventArgs e)
{
    ReadXML Reader = new ReadXML(tBOpenFile.Text);

    Words = new Dictionary<int, string>();
    Dictionary<KeyValuePair<int, int>, int> ReadedRelations = new Dictionary<KeyValuePair<int, int>, int>();

    // reading
    while(Reader.ReadNextWord())
    {
        Words.Add(Reader.CurrentWordId, Reader.CurrentWordName);

        foreach (KeyValuePair<KeyValuePair<int, int>, int> CurrentRelation in Reader.CurrentRelations)
        {
            ReadedRelations.Add(new KeyValuePair<int, int>(CurrentRelation.Key.Key, CurrentRelation.Key.Value), CurrentRelation.Value);
        }
    }

通过调试,我注意到应用程序启动非常快,并且随着时间的推移变慢

  • 前 10,000 个单词需要 7 秒
  • 前 200,000 个单词 30 分钟
  • 前 220,000 个单词 35 分钟

我无法解释这种行为!但我确信 XML 文件中的单词平均大小相同。也许Add()-方法会因字典长度而变慢。

如何加快申请速度?

【问题讨论】:

  • 查看 Linq to XML 而不是逐个节点阅读
  • 也许使用Dictionary&lt;int, Dictionary&lt;int, int&gt;&gt; 并使用双键索引两次而不是一次索引可能会有所帮助。取决于数据。
  • @Lloyd,这将如何帮助提高性能?
  • @svick 自己试试看
  • @harold:relation 是两个单词之间的连接,其权重是整数。我使用源词的Id和目标词的Id作为key。我认为这是有道理的,不是吗?

标签: c# xml performance dictionary


【解决方案1】:

编辑:好的,现在我已经运行了代码,我相信这是问题所在:

foreach (KeyValuePair<KeyValuePair<int, int>, int> CurrentRelation in 
         Reader.CurrentRelations)
{
    ReadedRelations.Add(new KeyValuePair<int, int>(CurrentRelation.Key.Key, 
        CurrentRelation.Key.Value), CurrentRelation.Value);
}

没有那个循环,它的工作速度要快得多...这让我怀疑您从 XML 读取的事实实际上是一个红鲱鱼。

我怀疑问题在于KeyValuePair&lt;,&gt; 没有覆盖EqualsGetHashCode。我相信,如果您创建自己的 RelationKey 值类型,其中包含两个 int 值并覆盖 GetHashCodeEquals(并实现 IEquatable&lt;RelationKey&gt;),它会快很多。

或者,您始终可以使用 long 来存储两个 int 值 - 有点小技巧,但它会很好用。我现在无法对此进行测试,但我会在有更多时间时试一试。

即使只是将循环更改为:

foreach (var relation in Reader.CurrentRelations)
{
    ReadedRelations.Add(relation.Key, relation.Value);
}

会更简单,效率更高...

编辑:这是RelationKey 结构的示例。只需将所有出现的KeyValuePair&lt;int, int&gt; 替换为RelationKey,并使用SourceTarget 属性代替KeyValue

public struct RelationKey : IEquatable<RelationKey>
{
    private readonly int source;
    private readonly int target;

    public int Source { get { return source; } }
    public int Target { get { return target; } }

    public RelationKey(int source, int target)
    {
        this.source = source;
        this.target = target;
    }

    public override bool Equals(object obj)
    {
        if (!(obj is RelationKey))
        {
            return false;
        }
        return Equals((RelationKey)obj);
    }

    public override int GetHashCode()
    {
        return source * 31 + target;
    }

    public bool Equals(RelationKey other)
    {
        return source == other.source && target == other.target;
    }
}

【讨论】:

  • 我认为它已经在BackgroundWorker 上运行,这就是为什么该方法被称为bgReader_DoWork
  • @JonSkeet:首先我忘了清除CurrentRelations,但后来我注意到了。之后我发布了这个问题。对不起,我忘记了那个重要的部分。我现在更新了我的问题代码。
  • @JonSkeet:我应该如何避免公共领域?我还有另一个backgroundworker,它在阅读后获取数据。
  • @sharethis:如果不出意外,您可以使用属性而不是字段。但最好让ReadNextWord 返回一个值,指示成功/失败、单词、ID 和关系。老实说,如果没有完整的代码,将很难诊断。理想情况下,将其转换为一个简短但完整的控制台应用程序(更容易上手)并发布,并附上一个示例 XML 文件的链接。
  • @JonSkeet:上传压缩的 Visual Studio 解决方案和 XML 文件有帮助吗?压缩后的 XML 文件只有 22mb 左右。
猜你喜欢
  • 1970-01-01
  • 2016-11-03
  • 2016-06-12
  • 2021-08-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-21
  • 1970-01-01
相关资源
最近更新 更多