Сжатие большого файла с помощью SharpZipLib, вызывающее исключение нехватки памяти

У меня есть XML-файл размером 453 МБ, который я пытаюсь сжать в ZIP- файл с помощью SharpZipLib.

Ниже приведен код, который я использую для создания почтового индекса, но это вызывает OutOfMemoryException, Этот код успешно сжимает файл размером 428 МБ.

Любая идея, почему происходит исключение, потому что я не понимаю, почему в моей системе достаточно памяти.

public void CompressFiles(List<string> pathnames, string zipPathname)
{
    try
    {
        using (FileStream stream = new FileStream(zipPathname, FileMode.Create, FileAccess.Write, FileShare.None))
        {
            using (ZipOutputStream stream2 = new ZipOutputStream(stream))
            {
                foreach (string str in pathnames)
                {
                    FileStream stream3 = new FileStream(str, FileMode.Open, FileAccess.Read, FileShare.Read);
                    byte[] buffer = new byte[stream3.Length];
                    try
                    {
                        if (stream3.Read(buffer, 0, buffer.Length) != buffer.Length)
                        {
                            throw new Exception(string.Format("Error reading '{0}'.", str));
                        }
                    }
                    finally
                    {
                        stream3.Close();
                    }
                    ZipEntry entry = new ZipEntry(Path.GetFileName(str));
                    stream2.PutNextEntry(entry);
                    stream2.Write(buffer, 0, buffer.Length);
                }
                stream2.Finish();
            }
        }
    }
    catch (Exception)
    {
        File.Delete(zipPathname);
        throw;
    }
}

2 ответа

Решение

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

Вот ваш код с буфером 4096 байт (и некоторой очисткой):

public static void CompressFiles(List<string> pathnames, string zipPathname)
{
    const int BufferSize = 4096;
    byte[] buffer = new byte[BufferSize];

    try
    {
        using (FileStream stream = new FileStream(zipPathname,
            FileMode.Create, FileAccess.Write, FileShare.None))
        using (ZipOutputStream stream2 = new ZipOutputStream(stream))
        {
            foreach (string str in pathnames)
            {
                using (FileStream stream3 = new FileStream(str,
                    FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    ZipEntry entry = new ZipEntry(Path.GetFileName(str));
                    stream2.PutNextEntry(entry);

                    int read;
                    while ((read = stream3.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        stream2.Write(buffer, 0, read);
                    }
                }
            }
            stream2.Finish();
        }
    }
    catch (Exception)
    {
        File.Delete(zipPathname);
        throw;
    }
}

Особенно обратите внимание на этот блок:

const int BufferSize = 4096;
byte[] buffer = new byte[BufferSize];
// ...
int read;
while ((read = stream3.Read(buffer, 0, buffer.Length)) > 0)
{
    stream2.Write(buffer, 0, read);
}

Это читает байты в buffer, Когда байтов больше нет, Read() Метод возвращает 0, вот когда мы остановимся. когда Read() успешно, мы можем быть уверены, что в буфере есть некоторые данные, но мы не знаем, сколько байтов. Может быть заполнен весь буфер или только небольшая его часть. Поэтому мы используем количество прочитанных байтов read определить, сколько байтов записать в ZipOutputStream,

Кстати, этот блок кода можно заменить простым оператором, добавленным в.Net 4.0, который делает то же самое:

stream3.CopyTo(stream2);

Итак, ваш код может стать:

public static void CompressFiles(List<string> pathnames, string zipPathname)
{
    try
    {
        using (FileStream stream = new FileStream(zipPathname,
            FileMode.Create, FileAccess.Write, FileShare.None))
        using (ZipOutputStream stream2 = new ZipOutputStream(stream))
        {
            foreach (string str in pathnames)
            {
                using (FileStream stream3 = new FileStream(str,
                    FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    ZipEntry entry = new ZipEntry(Path.GetFileName(str));
                    stream2.PutNextEntry(entry);

                    stream3.CopyTo(stream2);
                }
            }
            stream2.Finish();
        }
    }
    catch (Exception)
    {
        File.Delete(zipPathname);
        throw;
    }
}

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

Вы выделяете много памяти без веской причины, и я уверен, что у вас есть 32-битный процесс. 32-разрядные процессы могут выделять до 2 ГБ виртуальной памяти только в нормальных условиях, и библиотека, несомненно, также выделяет память.

Во всяком случае, здесь несколько вещей не так:

  • byte[] buffer = new byte[stream3.Length];

    Зачем? Вам не нужно хранить все это в памяти, чтобы обработать его.

  • if (stream3.Read(buffer, 0, buffer.Length) != buffer.Length)

    Этот противный. Stream.Read явно разрешено возвращать меньше байтов, чем вы просили, и это все еще действительный результат. При чтении потока в буфер вы должны вызвать Read несколько раз, пока буфер не заполнится или не будет достигнут конец потока.

  • Ваши переменные должны иметь более значимые имена. Вы можете легко потеряться с этими stream2, stream3 и т.п.

Простое решение будет:

using (var zipFileStream = new FileStream(zipPathname, FileMode.Create, FileAccess.Write, FileShare.None))
using (ZipOutputStream zipStream = new ZipOutputStream(zipFileStream))
{
    foreach (string str in pathnames)
    {
        using(var itemStream = new FileStream(str, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            var entry = new ZipEntry(Path.GetFileName(str));
            zipStream.PutNextEntry(entry);
            itemStream.CopyTo(zipStream);
        }
    }
    zipStream.Finish();
}
Другие вопросы по тегам