Опасность метода подстроки C #? - PullRequest
12 голосов
/ 08 ноября 2010

Недавно я читал о некоторых недостатках метода Java-подстроки - в частности, касающихся памяти и того, как java хранит ссылку на исходную строку.По иронии судьбы я также разрабатываю серверное приложение, которое использует реализацию подстроки в C # .Net много десятков раз в секунду.Это заставило меня задуматься ...

  1. Есть ли проблемы с памятью в C # (.Net) string.Substring?
  2. Какова производительность на string.Substring?Есть ли более быстрый способ разбить строку на основе начальной / конечной позиции?

Ответы [ 9 ]

18 голосов
/ 08 ноября 2010

Рассматривая реализацию .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;
}
3 голосов
/ 08 ноября 2010

Каждый раз, когда вы используете подстроку, вы создаете новый экземпляр строки - он должен копировать символ из старой строки в новую вместе с соответствующим выделением новой памяти & mdash; и не забывайте, что это символы Юникода. Это может быть или не быть плохой вещью - в какой-то момент вы все равно захотите использовать эти символы. В зависимости от того, что вы делаете, вам может понадобиться собственный метод, который просто находит нужные индексы в строке, которую вы можете использовать позже.

1 голос
/ 08 ноября 2010

Просто добавьте другую точку зрения на это.

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

Частое распределение / освобождение приведет к фрагментации памяти.GC не может быть в состоянии де-фрагментировать во времени, в зависимости от того, какие операции вы выполняете.Я знаю, что Server GC в .NET неплохо справляется с де-фрагментацией памяти, но вы всегда можете заморозить (не давая GC выполнить сбор) систему, написав плохой код.

1 голос
/ 08 ноября 2010

В случае утечки памяти Java, возникающей при использовании subString, это легко исправить, создав экземпляр нового объекта String с помощью конструктора копирования (то есть вызова формы "new String (String)").Используя это, вы можете отбросить все ссылки на оригинальную (и в случае, если это действительно большая проблема) строку и сохранить в памяти только те ее части, которые вам нужны.

Не идеально, вТеория JVM могла бы быть более умной и сжимать объект String (как было предложено выше), но это позволяет выполнить работу с тем, что мы имеем сейчас.

Что касается C #, как уже было сказано, эта проблема не 'не существует.

1 голос
/ 08 ноября 2010

всегда хорошо попробовать и измерить прошедшие миллисекунды.

Stopwatch watch = new Stopwatch();
watch.Start();
// run string.Substirng code
watch.Stop();
watch.ElapsedMilliseconds();
0 голосов
/ 08 ноября 2010

Для профилирования памяти во время разработки вы можете использовать этот код:

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

Удачи!;)

0 голосов
/ 08 ноября 2010

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

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

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

0 голосов
/ 08 ноября 2010

Реализация CLR (следовательно, C #) Substring не сохраняет ссылку на исходную строку, поэтому у нее нет проблемы утечки памяти в строках Java.

0 голосов
/ 08 ноября 2010

Кажется, я помню, что строки в Java хранились как фактические символы вместе с началом и длиной.

Это означает, что строка подстроки может иметь одни и те же символы (поскольку они являются неизменяемыми) и должны поддерживать только начало и длину.

Так что я не совсем уверен, какие проблемы с вашей памятью имеют строки Java.


Что касается статьи, опубликованной в вашем редакторе, мне кажется, что это не проблема.

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

Даже если у вас была строка 10M и вы создали 400 подстрок, вы используете только эти 10M для базового массива char - он не делает 400 копий этой подстроки. Единственное влияние памяти - это бит начала / длины каждого объекта подстроки.

Автор, похоже, жалуется, что они прочитали огромную строку в память, а затем хотели только ее немного, но все это было сохранено - я бы посоветовал им переосмыслить, как они обрабатывают свои данные :-)

Чтобы назвать это багом в Java, это тоже огромный шаг. Ошибка - это то, что не работает по спецификации. Это было преднамеренное дизайнерское решение для повышения производительности, нехватка памяти, потому что вы не понимаете, как все работает, это не ошибка, IMNSHO. И это определенно не утечка памяти.


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

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

К сожалению, это почти наверняка означало бы, что базовый массив char должен был бы хранить записи всех строковых объектов, которые ссылались на него, так что он мог бы выяснить, какие биты были не использованы и изменить все поля начала и длины строкового объекта.

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

Я думаю, что если память заканчивается, я бы предпочел, чтобы , а не поддерживал это отображение char-array-to-string, чтобы сделать возможным этот уровень GC, вместо этого я бы предпочел эту память для моих строк.


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

Не потому, что разработчики Java слишком ленивы, а потому, что это не проблема.

Вы можете свободно применять свои собственные строковые методы, которые соответствуют методам C # (которые не разделяют базовые данные, за исключением определенных ограниченных сценариев). Это исправит проблемы с памятью, но за счет снижения производительности, поскольку вам придется копировать данные каждый раз, когда вы вызываете подстроку. Как и большинство вещей в ИТ (и жизни), это компромисс.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...