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

Получая подстроку в .Net, ссылается ли новая строка на те же исходные строковые данные или данные копируются?

Предполагая, что у меня есть следующие строки:

string str1 = "Hello World!";  
string str2 = str1.SubString(6, 5); // "World"

Я надеюсь, что в приведенном выше примере str2 не копирует "Мир", а просто заканчивается тем, что является новой строкой, указывающей на одно и то же пространство памяти, только с началом смещения 6 и длиной 5.

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

4b9b3361

Ответ 1

Это новая строка.

Строки в .NET всегда неизменны. Всякий раз, когда вы создаете новую строку с помощью метода, включая Substring, он будет создавать новую строку в памяти. Единственный раз, когда вы сообщаете ссылки на одни и те же данные в строках в .NET, - это если вы явно назначаете строковую переменную другой строке (в которой копируете ссылку) или работаете со строковыми константами, которые обычно интернированы. Если вы знаете, что ваша строка будет делиться значением с интернированной строкой (константа/литерал из вашего кода), вы можете получить "общую" копию с помощью String.Intern.

Это хорошо, btw. Чтобы делать то, что вы описывали, для каждой строки требуется ссылка (для строковых данных), а также длина смещения+. Прямо сейчас они требуют только ссылки на строковые данные.

Это значительно увеличит размер строк в целом по всей структуре.

Ответ 2

Как отмечали другие, CLR делает копии при выполнении подстроки.

Как вы заметили, было бы возможно, чтобы строка была представлена ​​как внутренний указатель с длиной. Это делает операцию подстроки чрезвычайно дешевой.

Есть также способы сделать другие операции дешевыми. Например, конкатенация строк может быть дешевой, представляя строки как дерево подстрок.

В обоих случаях то, что происходит здесь, является результатом операции, а не собственно "результатом", само по себе, а скорее дешевым объектом, который представляет собой возможность получить результаты при необходимости.

Внимательный читатель только что понял, что так работает LINQ. Когда мы говорим

var results = from c in customers where c.City == "London" select c.Name;

"результаты" не содержат результатов запроса. Этот код возвращается почти сразу; Результаты содержат объект, представляющий запрос. Только при повторении запроса происходит дорогостоящий механизм поиска коллекции. Мы используем силу монадического представления семантики последовательности, чтобы отложить вычисления до конца.

Затем возникает вопрос: "Это хорошая идея сделать то же самое в строках?" и ответ звучит "нет". У меня много мучительных реальных экспериментов на этом. Однажды я провел лето, переписывая процедуры обработки строкой компилятора VBScript для хранения конкатенаций строк как дерева операций конкатенации строк; только когда результат фактически используется как строка, фактически происходит конкатенация. Это было катастрофой; дополнительное время и память, необходимые для отслеживания всех указателей строк, делали 99% -ый случай - кто-то делал несколько простых операций с маленькими строками для отображения веб-страницы - примерно в два раза медленнее, в то время как массовое ускорение крошечного, крошечного меньшинства страниц, которые были написаны с использованием наивных конкатенаций строк.

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

Кроме того, использование подхода "внутреннего указателя" для струн значительно усложняет сборщик мусора; идущий с таким подходом, похоже, делает вероятным, что GC будет замедляться в целом, что никому не приносит пользы. Вы должны посмотреть на общую стоимость воздействия изменения, а не только на его влияние на некоторые узкие сценарии.

Если у вас есть особые потребности в производительности из-за необычно больших данных, вам следует подумать о написании собственной специальной библиотеки строк, которая использует "монадический" подход, такой как LINQ. Вы можете представлять свои строки внутри массива char, а затем операции подстроки просто копируют ссылку на массив и меняют начальную и конечную позиции.

Ответ 3

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

Недавно я услышал о том, что называется "канаты", которые будут работать так, как вы предлагаете, но я не знаю никакой реализации в .NET.

http://en.wikipedia.org/wiki/Rope_(computer_science)

Ответ 4

Он ссылается на новую строку.

Ответ 5

Я знаю, что я ничего не знаю о .NET.

Но я хотел бы сделать наблюдение.

Большинство современных пакетов String имеют поведение "копировать на запись".

В частности, это означает, что если вы выделите подстроку, она будет использовать существующее хранилище родительской строки до тех пор, пока строка не понадобится изменить, и в этот момент она скопирует базовые данные в свое собственное пространство для использовать.

Теперь, если у вас есть неизменяемые строки, где базовые данные не могут измениться, нет никаких оснований НЕ делать этого. Нет способа "писать" в неизменяемую строку, поэтому ей даже не нужно копировать функции записи, просто делиться ею. С++ имеет изменяемые строки, поэтому они копируют при записи.

Например, Java делает это.

Обычно это хорошо. Это небольшое влияние на производительность.

Если вы этого не хотите, скажите в этом примере:

String big1MBString = readLongHonkinStringFromTheInterTubes();
static String ittyBitty = big1MBString.substring(1, 5);

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

Глядя на источники Mono, они фактически выделяют новую память. Таким образом, возможно,.NET является исключением из того, что сегодня является обычной практикой. Несомненно, у них есть свои обоснованные и обоснованные причины (т.е. Я не говорю, что .NET сделал это неправильно), просто... отличается от того, что делают другие.

Ответ 6

SubString создает новую строку. Таким образом будет выделена новая память для нового strin.

Ответ 7

В строках CLR неизменяемы, они не могут быть изменены. При манипулировании большими строками я бы предложил посмотреть на класс строковых построителей.

Ответ 8

как сказал Рид, строка неизменна. если вы имеете дело с длинными строками, подумайте об использовании StringBuilder, это может повысить производительность, в зависимости от того, что вы пытаетесь выполнить. если вы можете добавить некоторые детали к своему вопросу, вы наверняка получите предложение о наилучшей реализации.

Ответ 9

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

От MSDN: "Общее время исполнения в режиме реального времени автоматически поддерживает таблицу, называемую" пулом-пул ", которая содержит один экземпляр каждой уникальной константы строки литерала, объявленной в программе, а также любой уникальный экземпляр String, который вы добавляете программно.

Внутренний пул сохраняет хранилище строк. Если вы назначаете литеральную строковую константу нескольким переменным, каждая переменная устанавливается так, чтобы ссылаться на одну и ту же константу в станем пуле вместо ссылки на несколько разных экземпляров String, которые имеют одинаковые значения. "

Образец кода является информативным. Вы можете предотвратить автоматическое интернирование с использованием атрибута [assembly: CompilationRelaxations(CompilationRelaxations.NoStringInterning)] , чтобы предотвратить автоматическое интернирование строк. Вам также нужно будет использовать NGEN.exe для компиляции его на собственное изображение, чтобы предотвратить интернирование.

Обратите внимание: если вы используете StringBuilder, он избегает интернирования. Это только для строк, которые могут быть сопоставлены с другими строками, известными во время компиляции.

Это модифицированный пример статьи MSDN, обратите внимание, что если я передаю часть "abcd" из Консоли, он все еще интернирован, хотя str3 создается во время выполнения. Однако StringBuilder избегает интернирования.

// 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);
    }
}