Создать Zip-архив из нескольких файлов в памяти на С#

Есть ли способ создать Zip-архив, содержащий несколько файлов, когда файлы в данный момент находятся в памяти? Файлы, которые я хочу сохранить, на самом деле представляют собой только текст и хранятся в классе строк в моем приложении. Но я хотел бы сохранить несколько файлов в одном автономном архиве. Все они могут быть в корне архива.

Было бы неплохо иметь возможность сделать это с помощью SharpZipLib.


person Adam Haile    schedule 09.11.2008    source источник


Ответы (8)


Используйте для этого ZipEntry и PutNextEntry(). Ниже показано, как это сделать для файла, но для объекта в памяти просто используйте MemoryStream.

FileStream fZip = File.Create(compressedOutputFile);
ZipOutputStream zipOStream = new ZipOutputStream(fZip);
foreach (FileInfo fi in allfiles)
{
    ZipEntry entry = new ZipEntry((fi.Name));
    zipOStream.PutNextEntry(entry);
    FileStream fs = File.OpenRead(fi.FullName);
    try
    {
        byte[] transferBuffer[1024];
        do
        {
            bytesRead = fs.Read(transferBuffer, 0, transferBuffer.Length);
            zipOStream.Write(transferBuffer, 0, bytesRead);
        }
        while (bytesRead > 0);
    }
    finally
    {
        fs.Close();
    }
}
zipOStream.Finish();
zipOStream.Close();
person Cory    schedule 09.11.2008
comment
Можете ли вы использовать «using ()» для zipOStream вместо Close()? - person Jay Bazuzi; 21.12.2008
comment
Да, ZipOutputStream является одноразовым - person Mike; 10.08.2009
comment
Если вы используете .NET 4.5, есть встроенный класс Zip, который намного проще в использовании, чем эта мерзость (SharpZipLib). - person The Muffin Man; 27.08.2014
comment
Я думаю, что для этого нужно zipOStream.CloseEntry() после каждого перечисления (т.е. в конце foreach) -- github.com/icsharpcode/SharpZipLib/wiki/ - person drzaus; 09.06.2015
comment
@TheMuffinMan хотите связать/доработать альтернативу? Обертывает ли этот ответ? - person drzaus; 09.06.2015

Использование SharpZipLib для этого кажется довольно сложным. Это намного проще в DotNetZip. В v1.9 код выглядит так:

using (ZipFile zip = new ZipFile())
{
    zip.AddEntry("Readme.txt", stringContent1);
    zip.AddEntry("readings/Data.csv", stringContent2);
    zip.AddEntry("readings/Index.xml", stringContent3);
    zip.Save("Archive1.zip"); 
}

Приведенный выше код предполагает, что stringContent{1,2,3} содержит данные, которые должны храниться в файлах (или записях) в ZIP-архиве. Первая запись называется «Readme.txt» и хранится в «Каталоге» верхнего уровня в zip-архиве. Следующие две записи хранятся в каталоге «чтения» в zip-архиве.

Строки кодируются в кодировке по умолчанию. Существует не показанная здесь перегрузка AddEntry(), которая позволяет вам явно указать используемую кодировку.

Если у вас есть содержимое в виде потока или массива байтов, а не строки, существуют перегрузки для AddEntry(), которые принимают эти типы. Существуют также перегрузки, которые принимают делегат Write — ваш метод, который вызывается для записи данных в zip. Это работает, например, для простого сохранения DataSet в zip-файле.

DotNetZip является бесплатным и открытым исходным кодом.

person Cheeso    schedule 21.12.2008

Эта функция должна создать массив байтов из потока данных: для простоты я создал простой интерфейс для работы с файлами.

public interface IHasDocumentProperties
{
    byte[] Content { get; set; }
    string Name { get; set; }
}

public void CreateZipFileContent(string filePath, IEnumerable<IHasDocumentProperties> fileInfos)
{    
    using (var memoryStream = new MemoryStream())
    {
        using (var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
        {
            foreach(var fileInfo in fileInfos)
            {
                var entry = zipArchive.CreateEntry(fileInfo.Name);

                using (var entryStream = entry.Open())
                {
                    entryStream.Write(fileInfo.Content, 0, fileInfo.Content.Length);
                }                        
            }
        }

        using (var fileStream = new FileStream(filePath, FileMode.OpenOrCreate, System.IO.FileAccess.Write))
        {
            memoryStream.CopyTo(fileStream);
        }
    }
}
person johnny 5    schedule 12.07.2017
comment
Это здорово, за исключением того, что вы используете memoryStream после его удаления. - person John Gilmer; 26.04.2020
comment
Хороший улов, я исправил это сейчас - person johnny 5; 27.04.2020

Да, для этого вы можете использовать SharpZipLib — когда вам нужно предоставить поток для записи, используйте файл MemoryStream.

person Jon Skeet    schedule 09.11.2008

Я сталкиваюсь с этой проблемой, используя пример MSDN, я создал этот класс:

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.IO.Packaging;  
using System.IO;  

public class ZipSticle  
{  
    Package package;  

    public ZipSticle(Stream s)  
    {  
        package = ZipPackage.Open(s, FileMode.Create);  
    }  

    public void Add(Stream stream, string Name)  
    {  
        Uri partUriDocument = PackUriHelper.CreatePartUri(new Uri(Name, UriKind.Relative));  
        PackagePart packagePartDocument = package.CreatePart(partUriDocument, "");  

        CopyStream(stream, packagePartDocument.GetStream());  
        stream.Close();  
    }  

    private static void CopyStream(Stream source, Stream target)  
    {  
        const int bufSize = 0x1000;  
        byte[] buf = new byte[bufSize];  
        int bytesRead = 0;  
        while ((bytesRead = source.Read(buf, 0, bufSize)) > 0)  
            target.Write(buf, 0, bytesRead);  
    }  

    public void Close()  
    {  
        package.Close();  
    }  
}

Затем вы можете использовать его следующим образом:

FileStream str = File.Open("MyAwesomeZip.zip", FileMode.Create);  
ZipSticle zip = new ZipSticle(str);  

zip.Add(File.OpenRead("C:/Users/C0BRA/SimpleFile.txt"), "Some directory/SimpleFile.txt");  
zip.Add(File.OpenRead("C:/Users/C0BRA/Hurp.derp"), "hurp.Derp");  

zip.Close();
str.Close();

Вы можете передать MemoryStream (или любой Stream) в ZipSticle.Add, например:

FileStream str = File.Open("MyAwesomeZip.zip", FileMode.Create);  
ZipSticle zip = new ZipSticle(str);  

byte[] fileinmem = new byte[1000];
// Do stuff to FileInMemory
MemoryStream memstr = new MemoryStream(fileinmem);
zip.Add(memstr, "Some directory/SimpleFile.txt");

memstr.Close();
zip.Close();
str.Close();
person Ashleigh    schedule 07.10.2011

Обратите внимание, что этот ответ устарел; начиная с .Net 4.5 класс ZipArchive позволяет архивировать файлы в памяти. См. ответ Джонни 5 ниже, чтобы узнать, как его использовать.


Вы также можете сделать это немного по-другому, используя объект Serializable для хранения всех строк.

[Serializable]
public class MyStrings {
    public string Foo { get; set; }
    public string Bar { get; set; }
}

Затем вы можете сериализовать его в поток, чтобы сохранить его.
Чтобы сэкономить место, вы можете использовать GZipStream (From System.IO.Compression) для его сжатия. (примечание: GZip — это потоковое сжатие, а не архив из нескольких файлов).

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

person configurator    schedule 09.11.2008
comment
Я как бы хотел сделать файл сохранения в формате, который позволил бы пользователю открывать его и извлекать определенные фрагменты, но это может быть лучшим/самым простым... - person Adam Haile; 10.11.2008
comment
@Configurator Приятно видеть тебя здесь - person johnny 5; 12.07.2017

Я использовал ответ Cheeso, добавив MemoryStreams в качестве источника различных файлов Excel. Когда я скачал zip, в файлах ничего не было. Это может быть то, как мы пытались создать и загрузить файл через AJAX.

Чтобы содержимое разных файлов Excel было включено в Zip, мне пришлось добавить каждый из файлов как byte[].

using (var memoryStream = new MemoryStream())
using (var zip = new ZipFile())
{
    zip.AddEntry("Excel File 1.xlsx", excelFileStream1.ToArray());
    zip.AddEntry("Excel File 2.xlsx", excelFileStream2.ToArray());

    // Keep the file off of disk, and in memory.
    zip.Save(memoryStream);
}
person krillgar    schedule 20.09.2017

Используйте StringReader для чтения ваших строковых объектов и выставить их как Stream s.

Это должно облегчить передачу их в ваш код построения почтовых индексов.

person chakrit    schedule 09.11.2008