StringBuilder, используемый с PadLeft/Right OutOfMemoryException

Все, у меня есть следующее Append, которое я выполняю, когда создаю одну строку для фиксированного текстового файла.

formattedLine.Append(this.reversePadding ?
                     strData.PadLeft(this.maximumLength) :
                     strData.PadRight(this.maximumLength)); 

Это конкретное исключение происходит в PadLeft(), где this.maximumLength = 1,073,741,823 [длина поля NVARCHAR(MAX), полученная из SQL Server]. formattedLine = "101102AA-1" во время исключения, так почему это происходит. У меня должна быть максимально допустимая длина 2,147,483,647?

Мне интересно, будет ли здесь https://stackoverflow.com/a/1769472/626442 ответом, однако , я управляю любой памятью с помощью соответствующих Dispose() вызовов любых одноразовых объектов и using блоков, где это возможно.

Примечание. Этот фиксированный экспорт текста выполняется в фоновом потоке.

Спасибо за ваше время.


person MoonKnight    schedule 27.11.2012    source источник
comment
Ваш заголовок и ваше тело не синхронизированы — вы утверждаете, что это Append, который бросает заголовок, но затем в теле вы говорите, что это PadLeft. Я сильно подозреваю, что Append здесь неуместно.   -  person Jon Skeet    schedule 27.11.2012
comment
Согласованный. сейчас поменяю...   -  person MoonKnight    schedule 27.11.2012
comment
вызов Dispose для объектов не вызывает сборщик мусора. к вашему сведению.   -  person recursive    schedule 27.11.2012
comment
@рекурсивный я знаю. Я это понимаю, и нигде здесь я не предлагал, чтобы это было так. Однако спасибо за разъяснение.   -  person MoonKnight    schedule 28.11.2012


Ответы (3)


Это конкретное исключение происходит в PadLeft(), где this.maximumLength = 1 073 741 823.

Правильно. Итак, вы пытаетесь создать строку с более чем миллиардом символов.

Это не сработает, и я очень сильно сомневаюсь, что вы действительно этого хотите.

Обратите внимание, что каждый char в .NET состоит из двух байтов, а также строки в .NET заканчиваются нулем... и имеют некоторые другие поля помимо данных (например, длину). Это означает, что вам потребуется как минимум 2 147 483 652 байта + накладные расходы на объект, что превышает ограничение в 2 ГБ на объект.

Если вы работаете в 64-разрядной версии Windows, в .NET 4.5 есть специальный параметр app.config <gcAllowVeryLargeObjects>, что позволяет использовать массивы размером более 2 ГБ. Однако я не верю, что это изменит ваш конкретный вариант использования:

Использование этого элемента в файле конфигурации вашего приложения включает массивы размером более 2 ГБ, но не меняет другие ограничения на размер объекта или размер массива:

  • Максимальное количество элементов в массиве — UInt32MaxValue.

  • Максимальный индекс в любом отдельном измерении равен 2 147 483 591 (0x7FFFFFC7) для байтовых массивов и массивов однобайтовых структур и 2 146 435 071 (0X7FEFFFFF) для других типов.

  • Максимальный размер строк и других объектов, не являющихся массивами, не изменился.

Что бы вы хотели сделать с такой строкой после ее создания?

person Jon Skeet    schedule 27.11.2012
comment
Вероятно, это должен быть комментарий, а не ответ. Кроме того, он утверждает, что у него должно быть адресное пространство 2G, поэтому вполне возможно, что он имеет в виду работу с 1G. - person Eric J.; 27.11.2012
comment
Что касается вашего редактирования... почему вам требуется специальный параметр app.config для выделения таких больших массивов в 64-битных реализациях .NET? - person Eric J.; 27.11.2012
comment
@EricJ.: Честно говоря, не знаю, почему это только вариант. - person Jon Skeet; 27.11.2012
comment
Привет, Джон. Вы правы в том, что я не особенно хочу заполнять фиксированное текстовое поле такой длиной, но в настоящее время я пишу утилиту для экспорта ЛЮБОЙ таблицы базы данных. Это поле nText, и его максимальная длина собирается автоматически. Возможно, мне нужно посмотреть на ограничение этой максимальной длины. Спасибо за ваше время. - person MoonKnight; 27.11.2012
comment
@EricJ.: Я ничего не вижу об адресном пространстве 2G - я где-то здесь пропустил комментарий? В любом случае, я надеюсь, что теперь вы думаете, что мой ответ больше, чем комментарий :) - person Jon Skeet; 27.11.2012
comment
@JonSkeet: Да, ваш ответ хорошо вырос :-) В ОП говорится I should have a maximum allowed length of 2,147,483,647 - person Eric J.; 28.11.2012
comment
@EricJ.: Верно, но это может относиться, например, к тому, что String.Length является целым числом со знаком. Из сообщения неясно, откуда, по мнению ОП, возникнет ограничение. - person Jon Skeet; 28.11.2012

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

Фрагментация памяти может сделать это невозможным, особенно при использовании 32-разрядной реализации .NET.

person Eric J.    schedule 27.11.2012
comment
Спасибо за Ваш ответ. Я думал об этой причине. Но я справляюсь с любыми одноразовыми предметами вроде бы хорошо. Является ли вызов GC жизнеспособным вариантом или это мутная вода? Спасибо за ваше время... - person MoonKnight; 27.11.2012
comment
@Killercam: попробуйте сделать это с помощью профилировщика памяти Visual Studio. Вы увидите, что происходит с выделением памяти. - person Eric J.; 27.11.2012

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

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

Класс SqlDataReader предоставляет метод GetChars(), который позволяет читать фрагмент одной большой записи. Затем, вместо использования StringBuilder, возможно, используя StreamWriter (или какой-либо другой производный от TextWriter класс) для записи каждого фрагмента в вывод. Для этого потребуется только один полный буфер записи в пространстве памяти вашего приложения за один раз. Удачи!

person MarkPflug    schedule 27.11.2012
comment
Это хороший ответ, и это, по сути, то, что я делаю. Я использую SqlReader для чтения одной строки (я мог бы обрезать это до одного поля в строке). Я обнаружил, что проблема связана с заполнением по умолчанию, применяемым к полям, таким как NTEXT, которые могут быть большими (1 073 741 823). Я думаю, мне просто нужно более сдержанно обрабатывать отступы по умолчанию... Спасибо за ваше время. - person MoonKnight; 28.11.2012