C#Substring方法的危险?

时间:2010-11-08 02:34:13

标签: c# .net string memory-management performance

最近我一直在阅读Java子串方法的一些缺陷 - 特别是与内存有关,以及java如何保持对原始字符串的引用。具有讽刺意味的是,我也正在开发一个服务器应用程序,该应用程序在一秒钟内使用C#.Net的子串实现几十次。这让我想到了......

  1. C#(。Net)string.Substring是否存在内存问题?
  2. string.Substring的效果如何?是否有更快的方法根据开始/结束位置拆分字符串?

9 个答案:

答案 0 :(得分:18)

查看.NET的String.Substring实现,子字符串不与原始内容共享内存。

private unsafe string InternalSubString(int startIndex, int length, bool fAlwaysCopy)
{
    if (((startIndex == 0) && (length == this.Length)) && !fAlwaysCopy)
    {
        return this;
    }

    // Allocate new (separate) string
    string str = FastAllocateString(length);

    // Copy chars from old string to new string
    fixed (char* chRef = &str.m_firstChar)
    {
        fixed (char* chRef2 = &this.m_firstChar)
        {
            wstrcpy(chRef, chRef2 + startIndex, length);
        }
    }
    return str;
}

答案 1 :(得分:3)

每次使用子字符串时都会创建一个新的字符串实例 - 它必须将字符从旧字符串复制到新字符串以及相关的新内存分配 - 并且不要忘记这些是unicode字符。这可能不是坏事 - 在某些时候你想要在某个地方使用这些字符。根据您正在做的事情,您可能希望自己的方法仅在字符串中找到正确的索引,然后您可以使用它。

答案 2 :(得分:1)

尝试一下这总是好的测量经过的毫秒数。

Stopwatch watch = new Stopwatch();
watch.Start();
// run string.Substirng code
watch.Stop();
watch.ElapsedMilliseconds();

答案 3 :(得分:1)

在使用subString时可能会遇到Java内存泄漏的情况,通过使用复制构造函数实例化一个新的String对象(即调用“new String(String)”形式)可以轻松修复它。通过使用它,您可以丢弃对原始的所有引用(并且在这实际上是一个问题,相当大的情况下)String,并且仅在内存中维护它所需的部分。

不理想,理论上JVM可能更聪明并压缩String对象(如上所述),但这可以完成我们现在所做的工作。

至于C#,如前所述,这个问题不存在。

答案 4 :(得分:1)

只是为此添加另一个视角。

内存不足(大多数情况下)并不意味着你耗尽了所有内存。这意味着你的内存已经碎片化,下次你想分配一个块时,系统无法找到一个连续的内存块来满足你的需求。

频繁的分配/解除分配会导致内存碎片。 GC可能无法及时对您执行的操作进行解体。我知道.NET中的Server GC非常适合对内存进行分解,但是你可以通过编写错误代码来挨饿(阻止GC进行收集)系统。

答案 5 :(得分:0)

我似乎记得Java中的字符串存储为实际字符以及开头和长度。

这意味着子字符串字符串可以共享相同的字符(因为它们是不可变的),并且只需要保持单独的开始和长度。

所以我不完全确定你的内存问题与Java字符串有关。


关于您在编辑中发布的那篇文章,对我来说似乎有点不问题。

除非你习惯于制作大字符串,然后将它们中的一小部分字符串留下并将其放在周围,否则对内存的影响几乎为零。

即使你有一个10M字符串并且你创建了400个子字符串,你只需要将10M用于底层字符串数组 - 它不会生成该子字符串的400个副本。唯一的内存影响是每个子字符串对象的开始/长度位。

作者似乎在抱怨他们在内存中读取了一个巨大的字符串然后只想要了一点,但整个事情都被保留了 - 我的建议是他们可能想重新考虑他们如何处理他们的数据:-)

要称之为Java错误也是一个巨大的延伸。错误是对规范不起作用的东西。这是一个故意设计决策,旨在提高性能,耗尽内存,因为你不明白工作原理是不是一个错误,IMNSHO。它肯定内存泄漏。


在该文章的评论中有一个可能的好建议,GC可以通过压缩来更积极地恢复未使用的字符串。

这是你想要在第一次通过GC上做的事情,因为它会相对昂贵。但是,在其他所有GC操作都无法回收足够空间的情况下,您可以这样做。

不幸的是,它几乎肯定意味着底层char数组需要保留所有引用它的字符串对象的记录,因此它可以找出未使用的位修改所有字符串对象的起始和长度字段。

这本身可能会带来不可接受的性能影响,而且,如果你的内存太短而不能成为一个问题,你可能甚至无法为较小版本的字符串分配足够的空间。 / p>

我认为,如果内存耗尽,我可能更喜欢来维护这个char-array-to-string映射以使这个级别的GC成为可能,而我宁愿那样做用于我的字符串的内存。


由于有一个完全可以接受的解决方法,并且优秀的程序员应该知道他们选择的语言的缺点,我怀疑作者是对的 - 不会被修复。

不是因为Java开发人员太懒,而是因为它不是问题。

您可以自由地实现与C#匹配的自己的字符串方法(除了在某些有限的情况下,它们不共享基础数据)。这将解决您的内存问题,但代价是性能损失,因为每次调用substring时都必须复制数据。与IT(和生活)中的大多数事情一样,这是一种权衡。

答案 6 :(得分:0)

Substring的CLR(因此是C#)实现不保留对源字符串的引用,因此它没有Java字符串的“内存泄漏”问题。

答案 7 :(得分:0)

这些类型的字符串问题大多数是因为String是不可变的。 StringBuilder类适用于进行大量字符串操作时:

http://msdn.microsoft.com/en-us/library/2839d5h5(VS.71).aspx

请注意,真正的问题是内存分配而不是CPU,尽管过多的内存分配会占用CPU ......

答案 8 :(得分:0)

为了在开发过程中分析内存,可以使用以下代码:

bool forceFullCollection = false;

Int64 valTotalMemoryBefore = System.GC.GetTotalMemory(forceFullCollection);

//call String.Substring

Int64 valTotalMemoryAfter = System.GC.GetTotalMemory(forceFullCollection);

Int64 valDifferenceMemorySize = valTotalMemoryAfter - valTotalMemoryBefore;

关于参数 forceFullCollection :“如果forceFullCollection参数为true,则此方法在系统收集垃圾并完成对象时返回之前等待一小段时间。间隔的持续时间是内部指定的限制通过完成的垃圾收集周期的数量和周期之间恢复的内存量的变化。垃圾收集器不保证收集所有无法访问的内存。“ GC.GetTotalMemory Method

祝你好运!;)