Подтвердить что ты не робот

Почему string.Substring не использует общую память с исходной строкой?

Как мы все знаем, строки в .NET неизменяемы. (Ну, не на 100% полностью непреложный, но неизменный по дизайну и используемый как таковой любым разумным человеком, во всяком случае.)

Это делает его в основном ОК, что, например, следующий код просто сохраняет ссылку на ту же строку в двух переменных:

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

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

Console.WriteLine(x);
Console.WriteLine(y);

Вышеуказанные выходы:

sharp
sharp

Ясно, что x и y относятся к тому же объекту 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);

Вышеуказанные выходы:

shark
park
4b9b3361

Ответ 1

По двум причинам:

  • Метаданные строки (например, длина) хранятся в том же блоке памяти, что и символы, чтобы позволить одной строке использовать часть символьных данных другой строки, означало бы, что вам нужно будет выделить два блока памяти для большинства строк вместо одного. Поскольку большинство строк не являются подстроками других строк, это дополнительное выделение памяти будет больше потребляемой памяти, чем то, что вы могли бы получить, повторно используя часть строк.

  • После последнего символа строки сохраняется дополнительный символ NUL, чтобы строка также использовалась системными функциями, которые ожидают строку с нулевым завершением. Вы не можете поместить этот дополнительный символ NUL после подстроки, которая является частью другой строки.

Ответ 2

Я считаю, что строки С# имеют завершение с нулевой отметкой - в то время как это деталь реализации, которая не должна затрагивать управляемых потребителей, есть некоторые случаи (например, маршалинг), где это важно.

Также, если подстрока поделилась буфером с гораздо более длинной строкой, это означает, что ссылка на короткую подстроку помешала бы собирать более длинную строку. И возможность гнездования крыс ссылок на строки, ссылающиеся на один и тот же буфер.

Ответ 3

Чтобы добавить к другим ответам:

По-видимому, стандартные классы Java делают это: строка, возвращаемая String.substring(), повторно использует внутренний массив символов исходной строки (source, или посмотрите на источники JDK Sun).

Проблема заключается в том, что это означает, что исходная строка не может быть GCed до тех пор, пока все подстроки не будут пригодны для GC (поскольку они разделяют массив символов поддержки). Это может привести к растрате памяти, если вы начинаете с большой строки и извлекаете из нее несколько меньших строк, а затем отбрасываете большую строку. Это было бы общим при анализе входного файла, например.

Конечно, умный GC может обойти это, скопировав массив символов, когда он того стоит (Sun JVM может это сделать, я не знаю), но добавленная сложность может быть причиной не для реализации этого вообще говоря.

Ответ 4

Существует несколько способов, которыми можно было бы реализовать что-то вроде String:

  1. Объект "String" эффективно содержит массив с импликацией, что все символы в массиве находятся в строке. Это то, что на самом деле делает .net.
  2. Пусть каждый "String" является классом, который содержит ссылку на массив вместе со стартовым смещением и длиной. Проблема. Создание большинства строк потребует создания двух объектов, а не одного.
  3. Пусть каждая "String" будет структурой, которая содержит ссылку на массив вместе со стартовым смещением и длиной. Проблема. Назначения полям типа строки больше не будут атомарными.
  4. Есть два или более типа объектов "String" - те, которые содержат все символы в массиве, и те, которые содержат ссылку на другую строку вместе со смещением и длиной. Проблема: для этого потребовалось бы множество виртуальных методов.
  5. У каждого "String" есть специальный класс, который включает начальное смещение и длину, ссылку на объект, который может или не может быть одним и тем же объектом, и встроенный массив символов. Это приведет к сокращению пространства в общем случае, когда строка содержит свои собственные символы (потому что все они), но позволит одному и тому же коду работать со строками, которые содержат свои собственные символы или строки, которые "заимствуют" у других.
  6. Имейте универсальный объект ImmutableArray <T> тип (который наследует ReadableArray <T> ) и имеет значение ImmutableArray <Char> быть взаимозаменяемым со String. Существует много применений для неизменяемых массивов; Строка, вероятно, является наиболее распространенным случаем использования, но вряд ли единственным.
  7. Имейте универсальный тип ImmutableArray <T> типа, как указано выше, но также ImmutableArraySegment <T> класс, оба наследуются от ImmutableArrayBase <T> . Это потребует, чтобы многие методы были виртуальными, и, вероятно, это была бы моя любимая возможность.

    Обратите внимание, что большинство из этих подходов имеют значительные ограничения, по крайней мере, в некоторых сценариях использования.

Ответ 5

Я считаю, что это оптимизация CLR, которая не имеет ничего общего с программистами, поскольку вы не должны делать то, что делаете. Вы должны считать, что это новая строка каждый раз (как программист).

Ответ 6

после просмотра метода подстроки с отражателем я понял, что если вы пройдете 0 в методе substriong - он вернет тот же объект.

[SecurityCritical]
private unsafe string InternalSubString(int startIndex, int length, bool fAlwaysCopy)
{
    if (((startIndex == 0) && (length == this.Length)) && !fAlwaysCopy)
    {
        return this;
    }
    string str = FastAllocateString(length);
    fixed (char* chRef = &str.m_firstChar)
    {
        fixed (char* chRef2 = &this.m_firstChar)
        {
            wstrcpy(chRef, chRef2 + startIndex, length);
        }
    }
    return str;
}

Ответ 7

Это добавило бы сложность (или, по крайней мере, большее число умений) в таблицу intern. Представьте, что у вас уже есть две записи в рабочей таблице "Ожидание" и "Изгиб" и следующий код:

var x = "pending";
var y = x.Substring(1);

какая запись в рабочей таблице будет считаться хитом?