为什么不string.Substring与源字符串共享内存?

Dan*_*Tao 37 .net c# string substring immutability

众所周知,.NET中的字符串是不可变的.(好吧,不是100%完全不可变,但设计不可变,无论如何都被任何理性的人使用.)

这使得基本上可以,例如,以下代码只在两个变量中存储对同一字符串的引用:

string x = "shark";
string y = x.Substring(0);

// Proof:
fixed (char* c = y)
{
    c[4] = 'p';
}

Console.WriteLine(x);
Console.WriteLine(y);
Run Code Online (Sandbox Code Playgroud)

以上输出:

sharp
sharp
Run Code Online (Sandbox Code Playgroud)

显然,xy指向同一个string对象.所以这是我的问题:为什么不Substring 总是与源字符串共享状态?字符串本质上是char*一个长度的指针,对吗?所以在我看来,至少在理论上应该允许分配单个内存块来保存5个字符,其中两个变量只指向该(不可变)块内的不同位置:

string x = "shark";
string y = x.Substring(1);

// Does c[0] point to the same location as x[1]?
fixed (char* c = y)
{
    c[0] = 'p';
}

// Apparently not...
Console.WriteLine(x);
Console.WriteLine(y);
Run Code Online (Sandbox Code Playgroud)

以上输出:

shark
park
Run Code Online (Sandbox Code Playgroud)

Guf*_*ffa 25

有两个原因:

  • 字符串元数据(例如长度)与字符存储在同一个内存块中,以允许一个字符串使用另一个字符串的部分字符数据,这意味着您必须为大多数字符串而不是一个字符串分配两个内存块.由于大多数字符串不是其他字符串的子字符串,因此额外的内存分配将比通过重用部分字符串获得的内存消耗更多.

  • 在字符串的最后一个字符之后存储了一个额外的NUL字符,以使该字符串也可以被期望空终止字符串的系统函数使用.您不能在另一个字符串的子字符串后面添加额外的NUL字符.


Joe*_*Joe 12

我相信C#字符串是空终止的 - 虽然这是一个不应该与托管消费者有关的实现细节,但在某些情况下(例如编组)它很重要.

此外,如果子字符串共享具有更长字符串的缓冲区,则这意味着对短子字符串的引用将阻止收集更长的字符串.并且大鼠嵌套字符串引用的可能性指的是相同的缓冲区.


sle*_*ske 6

要添加到其他答案:

显然,Java标准类执行此操作:通过String.substring()重用原始字符串的内部字符数组(,或查看Sun的JDK源)返回的字符串.

问题是,这意味着在所有子字符串也符合GC条件之前,不能对原始字符串进行GC操作(因为它们共享后备字符数组).如果你从一个大字符串开始,并从中提取一些较小的字符串,然后丢弃大字符串,这可能会导致内存浪费.例如,在解析输入文件时这很常见.

当然,一个聪明的GC可能会通过复制字符数组来解决这个问题(Sun JVM可能会这样做,我不知道),但增加的复杂性可能是不实现此共享行为的原因.所有.