Недостаточно памяти с помощью BinaryWriter на C # при добавлении файлов в zip-файл

Я пытаюсь добавить файлы в Zip-файл, сохранив каталог. Приведенный ниже код в основном работает до тех пор, пока у меня нет файлов размером в несколько 100 Мбайт для архивирования. Если я просто заархивирую каталог с одним файлом размером около 250 МБ (в системе с большим количеством памяти, кстати), я получу исключение OutOfMemory в строке write.Write().

Я уже изменил код для чтения по частям, поскольку он сначала не удался, когда я прочитал / записал весь файл. Я не знаю, почему до сих пор не удается?

    using (FileStream zipToOpen = new FileStream(cZipName, eFileMode)) 
        ZipArchiveEntry readmeEntry = archive.CreateEntry(cFileToBackup

);

    using (BinaryWriter writer = new BinaryWriter(readmeEntry.Open()))
    {
        FileStream fsData = null;                                                                // Load file into FileStream
        fsData = new FileStream(cFileFull, FileMode.Open, FileAccess.Read);
        {
            byte[] buffer = new byte[1024];
            int bytesRead = 0;
            while ((bytesRead = fsData.Read(buffer, 0, buffer.Length)) > 0)
            {
                 writer.Write(buffer,0,bytesRead); // here it fails
                 fsData.Flush(); // ->CHANGED  THIS TO writer.Flush() SOLVED IT - nearly..
            }
        }
        fsData.Close();
    }

РЕДАКТИРОВАТЬ: Аркадиуш К. был прав в том, что я использовал промывку для ридера, а не для писателя. После изменения этого параметра программа заархивирует файлы размером 1 Гб и более там, где сначала остановилась на 100 Мб. Однако я получаю другое исключение, когда пытаюсь заархивировать, например. файл размером 6 ГБ - он останавливается на: System.IO.IOException не обрабатывается Поток был слишком длинным Source = mscorlib StackTrace: в System.IO.MemoryStream.Write (буфер Byte [], смещение Int32, Int32 количество) (и т. д.)

Кто-нибудь знает, почему он все еще не работает? Я бы сказал, что код теперь должен правильно читать и писать по 1 КБ за раз?


person Dick    schedule 12.09.2015    source источник
comment
Не могли бы вы также вставить скопированное сообщение об ошибке?   -  person displayName    schedule 12.09.2015
comment
Какова целевая платформа для вашего приложения? Если это x86, вы должны помнить об адресном пространстве памяти, которое 32-bit приложение может использовать в адресном пространстве (это около 2 ГБ, но, насколько мне известно, оно может быть расширено некоторыми параметрами)   -  person Arkadiusz K    schedule 12.09.2015
comment
Ошибка: {Возникло исключение типа 'System.OutOfMemoryException.} (-2147024882 mscorlib) в System.IO.MemoryStream.set_Capacity (значение Int32) в System.IO.MemoryStream.EnsureCapacity (значение Int32) в System.IO .MemoryStream.Write (буфер Byte [], смещение Int32, счетчик Int32) Он нацелен на любой процессор. Интересно то, что когда я меняю его на X64, ошибка меняется на Необработанное исключение типа «System.IO.IOException» в mscorlib.dll - Дополнительная информация: поток был слишком длинным. В обоих случаях должно работать, добавляя по 1 Кб за раз?   -  person Dick    schedule 12.09.2015
comment
Хм, тебе лучше сделать writer.Flush() вместо fsData.Flush(). Вы только читаете из FileStream, а не пишете в него.   -  person Arkadiusz K    schedule 13.09.2015
comment
ДА, вот оно. Я был так сконцентрирован на предположении, что с концепцией все еще что-то не так, что полностью упустил из виду неправильный флеш. Большое спасибо за Ваш ответ! Хотя запись работает нормально, большие файлы, которые не удалось выполнить, теперь работают, следующая проблема - чтение. Если файлы для чтения слишком велики, я получаю исключение. Stream was too long. Похоже, это тоже требует внимания.   -  person Dick    schedule 14.09.2015


Ответы (2)


Прежде всего, мне бы очень хотелось отформатировать ваш код и сделать его максимально лаконичным:

var readmeEntry = archive.CreateEntry(cFileToBackup);
using (var fsData = new FileStream(cFileFull, FileMode.Open, FileAccess.Read))
using (var writer = new BinaryWriter(readmeEntry.Open()))
{
    var buffer = new byte[1024];
    int bytesRead;
    while ((bytesRead = fsData.Read(buffer, 0, buffer.Length)) > 0)
    {
         writer.Write(buffer, 0, bytesRead); // here it fails
         writer.Flush();
    }
}

Теперь, чтобы объяснить, почему это не удается:

BinaryWriter - это средство записи потока. Когда ему нужно записать данные в поток, он обычно записывает их с префиксом длины и:

С префиксом длины означает, что этот метод сначала записывает длину строки в байтах при кодировании с текущей кодировкой экземпляра BinaryWriter в поток. Это значение записывается как целое число без знака. Затем этот метод записывает это количество байтов в поток.

Для записи в файл в вашем случае данные сначала записываются в MemoryStream. Здесь MemoryStream - это поток резервного хранилища. См. Схему ниже:

Потоки в .NET

(Изображение взято с: http://kcshadow.net/wpdeveloper/sites/default/files/streamd3.png)

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

person displayName    schedule 15.09.2015
comment
Спасибо за подробное объяснение. Остается вопрос: как я могу это решить? Я бы сказал, что должна быть возможность читать файлы, размер которых намного превышает размер памяти, и добавлять их в двоичный поток. - person Dick; 16.09.2015
comment
@Dick: Я не уверен, так как сам не пробовал, но вы можете попробовать изменить резервное хранилище на File вместо Memory. - person displayName; 16.09.2015
comment
@ Дик: Я бы хотел посмотреть, как вы определили archive. - person displayName; 16.09.2015
comment
@ displayName Я бы сказал, что единственное, что происходит в памяти, - это буферизация этого 1 КБ? Я использую файловый поток для чтения и Ziparchive для записи. Я добавил 1 строку кода выше; архив определяется в коде как archive = new ZipArchive - person Dick; 16.09.2015
comment
@Dick: это не 1 КБ в буферизации памяти. Ваш код пытается накопить результат в памяти, а затем, наконец, записать его обратно, когда закончите. - person displayName; 16.09.2015

Что касается вашего EDIT: столкнулся с той же проблемой. После некоторого покопания я обнаружил, что zipFileEntry.Open() возвращает WrappedStream, который является базовым потоком (тот, который не может быть сброшен до тех пор, пока не закончит запись в него).

Это WrappedStream проблема: его максимальная длина составляет ~ 2 ГБ. Я не мог найти способ обойти это, поэтому в итоге я вообще использовал другую библиотеку сжатия.

person Remus Dutulescu    schedule 27.03.2018
comment
Раздумали, какую библиотеку сжатия вы в конечном итоге использовали? - person Josiah Nunemaker; 06.08.2020