【问题标题】:C#: Insert strings to another string - performance issueC#:将字符串插入另一个字符串 - 性能问题
【发布时间】:2011-08-28 08:19:38
【问题描述】:

我有一个很长的字符串,以及一个索引和值的排序字典。我应该检查字典中的元素并将值插入字符串中的指定索引。我写了以下代码,效果很好,但是很慢:

private string restoreText(string text){
  StringBuilder sb = new StringBuilder(text);
  foreach(KeyValuePair<int, string> pair in _tags){
    sb.Insert(pair.Key, pair.Value);
  }
  return sb.ToString();
}

字典可能非常大,包含 500,000 个元素。 我认为使这个函数变慢的原因是 Insert() 方法。对于 100,000 个元素的字典,大约需要 5 秒。

有没有更高效的方法来写这个方法?

谢谢,

玛雅

【问题讨论】:

  • 如果将_tags 添加到字典中,text 中的内容是什么?

标签: c# string performance stringbuilder


【解决方案1】:

更好的方法是对要插入的项目进行排序,然后一个接一个地追加。

由于您没有对重叠发表评论,也许您首先对项目进行了排序?

【讨论】:

  • 这可能会更快,但我认为没有任何保证。无论哪种方式,简单的分析都可以确定。
  • 肯定会更快,因为 StringBuilder 是为追加而设计的,每次插入都必须将内存块向下移动。
【解决方案2】:

您的原始代码将根据从 _tags 返回项目的顺序给出不同的结果;我非常怀疑这不是你的意图。

相反,将标签按顺序排序,然后以正确的顺序将它们添加到字符串生成器中:

private string restoreText(string text)
{
    StringBuilder sb = new StringBuilder();
    foreach( KeyValuePair<int, string> pair in _tags.OrderBy(t => t.Key))
    {
        sb.Append(pair.Value);
    }

    return sb.ToString();
}

如果您真的想尽快完成此操作,请预先初始化 StringBuilder 的容量:

    StringBuilder sb = new StringBuilder(_tags.Sum(k => k.Value.Length));

更新

我错过了最初用于初始化StringBuildertext 参数。

为了避免在内存中打乱文本(由StringBuilder.Insert() 引起),我们希望坚持使用StringBuilder.Append()

我们可以通过将原始文本转换为另一个KeyValuePair实例序列,将它们与原始列表合并并按顺序处理。

看起来像这样(note:即席代码):

private string restoreText(string text)
{
    var textPairs 
        = text.Select( (c,i) => new KeyValuePair<int,string>(i, (string)c));
    var fullSequence
        = textPairs.Union(_tags).OrderBy(t => t.Key);
    StringBuilder sb = new StringBuilder();
    foreach( KeyValuePair<int, string> pair in fullSequence)
    {
        sb.Append(pair.Value);
    }

    return sb.ToString();
}

注意 - 我对你的上下文做了一大堆假设,所以这可能不适合你。请特别注意,.Union() 将丢弃重复项,尽管有一些简单的解决方法。

【讨论】:

  • 但是这里(文本)参数在哪里?
【解决方案3】:

如果您设置了索引以便插入不会更改其他索引,但正如您的代码说“是”,我也会假设。

你能测试一下吗:

private string RestoreText(string text)
{
    var sb = new StringBuilder();
    var totalLen = 0;
    var orgIndex = 0;
    foreach (var pair in _tags.OrderBy(t => t.Key))
    {
        var toAdd = text.Substring(orgIndex, pair.Key - totalLen);
        sb.Append(toAdd);
        orgIndex += toAdd.Length;
        totalLen += toAdd.Length;

        sb.Append(pair.Value);
        totalLen += pair.Value.Length;
    }
    if (orgIndex < text.Length) sb.Append(text.Substring(orgIndex));
    return sb.ToString();
}

它只在与原始代码相同的情况下使用追加

【讨论】:

    【解决方案4】:

    我不知道您的数据如何。

    但在我的测试中,它运行得很快(564 毫秒)。

            Dictionary<int, string> _tags = new Dictionary<int, string>();
            for (int i = 0; i < 1000000; i++)
            {
                _tags.Add(i, i.ToString().Length + "");
            }
    
            string text = new String('a' , 50000000);
            Console.WriteLine("****************************************");
    
            System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
    
            StringBuilder sb = new StringBuilder(text);
            foreach (KeyValuePair<int, string> pair in _tags)
            {
                sb.Insert(pair.Key, pair.Value);
            }
    
            sw.Stop();
    
            Console.WriteLine("sw:" + sw.ElapsedMilliseconds);
            Console.ReadKey();
    

    如果你可以使用 append() 代替 insert() ,它只需要 35ms...

    【讨论】:

    • 考虑在他的示例 StringBuilder 中已经创建了一些字符串。该字符串可能很长。 “新的 StringBuilder(文本)”。
    • 哦,是的。如果长度大于StringBuilder的Capacity会很慢。但你可以自己重置容量:)
    • 我的意思是,在大字符串的情况下,从性能的角度来看,插入可能会受到影响。
    • 我不知道他的文字有多大。但我测试了字符串 text = new String('a' , 100000000); ,但结果是一样的。
    • 谢谢大家的回复。字典按关键字排序。这些值不相互重叠。问题是我应该在参数文本中的指定索引(键)处插入值,而不仅仅是连接它们。这就是为什么在我的代码中使用参数“text”初始化 StringBuilder 的原因。任何进一步的帮助将不胜感激。谢谢,玛雅
    猜你喜欢
    • 1970-01-01
    • 2015-02-11
    • 1970-01-01
    • 2016-02-07
    • 1970-01-01
    • 2012-09-25
    • 2011-05-13
    • 2015-04-04
    相关资源
    最近更新 更多