C# 在.Net中获取子字符串时,新字符串是引用相同的原始字符串数据还是复制数据?

C# 在.Net中获取子字符串时,新字符串是引用相同的原始字符串数据还是复制数据?,c#,.net,string,substring,C#,.net,String,Substring,假设我有以下字符串: string str1 = "Hello World!"; string str2 = str1.SubString(6, 5); // "World" 我希望在上面的示例中,str2不会复制“World”,而只是成为一个新字符串,它指向相同的内存空间,只以偏移量6和长度5开头 实际上,我正在处理一些可能非常长的字符串,出于性能原因,我对这些字符串在幕后的工作方式很感兴趣。我对IL不太熟悉,无法深入了解这一点。它引用了一个全新的字符串。它是一个新字符串 NET中的字符

假设我有以下字符串:

string str1 = "Hello World!";  
string str2 = str1.SubString(6, 5); // "World"
我希望在上面的示例中,str2不会复制“World”,而只是成为一个新字符串,它指向相同的内存空间,只以偏移量6和长度5开头


实际上,我正在处理一些可能非常长的字符串,出于性能原因,我对这些字符串在幕后的工作方式很感兴趣。我对IL不太熟悉,无法深入了解这一点。

它引用了一个全新的字符串。

它是一个新字符串

NET中的字符串总是不可变的。无论何时通过方法(包括子字符串)生成新字符串,它都会在内存中构造新字符串。在.NET中共享对字符串中相同数据的引用的唯一时间是,如果将字符串变量显式分配给另一个字符串(在该字符串中复制引用),或者如果使用字符串常量(通常是插入的)。如果您知道您的字符串将与内部字符串(代码中的常量/文本)共享一个值,则可以通过检索“共享”副本

顺便说一句,这是一件好事——为了实现您所描述的,每个字符串都需要一个引用(对字符串数据)以及一个偏移量+长度。现在,它们只需要对字符串数据的引用


这通常会在整个框架中显著增加字符串的大小。

子字符串会创建一个新字符串。因此,将为新strin分配新内存。

它将创建一个新字符串,但这是一个非常明智的问题,并非不可想象。然而,我认为大多数情况下的性能损失将远远超过少数情况下的内存节省

我最近听说了一种叫做“ropes”的东西,它可以按照您建议的方式工作,但我不知道在.NET中有任何实现


在CLR中,字符串是不可变的,这意味着它们不能更改。
在处理大型字符串时,我建议使用字符串生成器类。

正如里德所说,字符串是不可变的。
如果你处理的是长串,考虑使用StrugBu建器,它可能会提高性能,当然取决于你想要完成什么。如果你能为你的问题添加一些细节,你肯定会得到关于最佳实现的建议。

你知道吗,我对.NET一无所知

但是,我想做一个观察

大多数现代字符串包都有“写时复制”行为

具体来说,这意味着如果您分配一个子字符串,它将使用父字符串的现有存储,直到该字符串需要更改为止,此时它将把底层数据复制到它自己的新空间中以供使用

现在,如果您有不可变的字符串,其中基础数据不能更改,那么没有理由不这样做。无法“写入”不可变字符串,因此它甚至不需要写时复制函数,只需共享即可。C++具有可变字符串,因此它们在写上复制。 例如,Java就是这样做的

通常这是件好事。对性能几乎没有影响

但是,在本例中,您不希望发生这种情况:

String big1MBString = readLongHonkinStringFromTheInterTubes();
static String ittyBitty = big1MBString.substring(1, 5);
现在有一个“5个字符”的字符串,它消耗1MB内存,因为它共享大字符串的基础1MB字符串缓冲区,但它只显示为一个5个字符的字符串。由于在内部保留了对较大字符串的引用,因此将“永远”释放出原始空间


看看Mono源,事实上,它们确实分配了新的内存。因此,也许.NET是当今普遍做法的一个例外。毫无疑问,他们有充分的理由(即,我不是说.NET做错了),只是…与其他人的做法不同。

正如其他人所指出的,CLR在执行子字符串操作时会进行复制

正如您所注意到的,字符串当然可以表示为具有长度的内部指针。这使得子字符串操作非常便宜

还有一些方法可以让其他业务变得便宜。例如,通过将字符串表示为子字符串树,可以降低字符串连接的成本

在这两种情况下,这里发生的是操作的结果,实际上不是“结果”本身,而是一个廉价的对象,它表示在需要时获得结果的能力

细心的读者会刚刚意识到LINQ就是这样工作的。当我们说

var results = from c in customers where c.City == "London" select c.Name;
“结果”不包含查询的结果。此代码几乎立即返回;结果包含一个表示查询的对象。只有当查询被迭代时,搜索集合的昂贵机制才会加速。我们使用序列语义的一元表示的能力将计算推迟到以后

然后问题变成了“在弦上做同样的事情是个好主意吗?”答案是响亮的“不”。我在这方面做了很多痛苦的现实实验。我曾经花了一个夏天重写VBScript编译器的字符串处理例程,将字符串连接存储为字符串连接操作树;只有当结果实际用作字符串时,才会发生连接。这是灾难性的;跟踪所有字符串指针所需的额外时间和内存使99%的情况下(某人执行一些简单的小字符串操作来呈现网页)的速度提高了一倍左右,同时大大加快了使用简单字符串连接编写的极少数页面的速度

NET程序中绝大多数真实的字符串操作都非常快;它们向下编译到内存移动,在正常情况下,这些移动会很好地保留在处理器缓存的内存块中,并且不会发生
// Sample for String.IsInterned(String)
using System;
using System.Text;
using System.Runtime.CompilerServices;
using System.Diagnostics;

// In the .NET Framework 2.0 the following attribute declaration allows you to 
// avoid the use of the interning when you use NGEN.exe to compile an assembly 
// to the native image cache.
//[assembly: CompilationRelaxations(CompilationRelaxations.NoStringInterning)]
class Sample
{
    public static void Main()
    {
        // String str1 is known at compile time, and is automatically interned.
        String str1 = "abcd";
        Console.WriteLine("Type cd and it will be ok, type anything else and Assert will fail.");
        string end = Console.ReadLine(); // Constructed, but still interned.
        string str3 = "ab" + end;

        // Constructed string, str2, is not explicitly or automatically interned.
        String str2 = new StringBuilder().Append("wx").Append("yz").ToString();
        Console.WriteLine();
        Test(1, str1);
        Test(2, str2);
        Test(3, str3);

        // Sanity checks. 
        // Debug.Assert(Object.ReferenceEquals(str3, str1)); // Assertion fails, as expected.
         Debug.Assert(Object.ReferenceEquals(string.Intern(str3), string.Intern(str1))); // Passes
         Debug.Assert(Object.ReferenceEquals(string.Intern(str3), (str1))); // Passes
         Debug.Assert(Object.ReferenceEquals((str3), string.Intern(str1))); // Fails
         Console.ReadKey();
    }

    public static void Test(int sequence, String str)
    {
        Console.Write("{0}) The string, '", sequence);
        String strInterned = String.IsInterned(str);
        if (strInterned == null)
            Console.WriteLine("{0}', is not interned.", str);
        else
            Console.WriteLine("{0}', is interned.", strInterned);
    }
}