38

ZipArchive 是 ZipArchiveEntries 的集合,添加/删除“条目”效果很好。但似乎没有目录/嵌套“档案”的概念。理论上,该类与文件系统分离,因为您可以完全在内存流中创建存档。但是,如果您希望在存档中添加目录结构,则必须在条目名称前加上路径。

问题:您将如何扩展 ZipArchive 以创建更好的界面来创建和管理目录?

例如,当前将文件添加到目录的方法是使用目录路径创建条目:

var entry = _archive.CreateEntry("directory/entryname");

而沿着这些思路的东西对我来说似乎更好:

var directory = _archive.CreateDirectoryEntry("directory");
var entry = _directory.CreateEntry("entryname");
4

11 回答 11

58

您可以使用以下内容,换句话说,手动创建目录结构:

using (var fs = new FileStream("1.zip", FileMode.Create))
using (var zip = new ZipArchive(fs, ZipArchiveMode.Create))
{
    zip.CreateEntry("12/3/"); // just end with "/"
}
于 2013-09-25T12:16:45.660 回答
21

我知道我迟到了(7.25.2018),

即使使用递归目录,这对我来说也完美无缺。

首先,记得安装 NuGet 包:

Install-Package System.IO.Compression

然后,扩展文件ZipArchive

 public static class ZipArchiveExtension {

     public static void CreateEntryFromAny(this ZipArchive archive, string sourceName, string entryName = "") {
         var fileName = Path.GetFileName(sourceName);
         if (File.GetAttributes(sourceName).HasFlag(FileAttributes.Directory)) {
             archive.CreateEntryFromDirectory(sourceName, Path.Combine(entryName, fileName));
         } else {
             archive.CreateEntryFromFile(sourceName, Path.Combine(entryName, fileName), CompressionLevel.Fastest);
         }
     }

     public static void CreateEntryFromDirectory(this ZipArchive archive, string sourceDirName, string entryName = "") {
         string[] files = Directory.GetFiles(sourceDirName).Concat(Directory.GetDirectories(sourceDirName)).ToArray();
         archive.CreateEntry(Path.Combine(entryName, Path.GetFileName(sourceDirName)));
         foreach (var file in files) {
             archive.CreateEntryFromAny(file, entryName);
         }
     }
 }

然后你可以打包任何东西,无论是文件还是目录:

using (var memoryStream = new MemoryStream()) {
    using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) {
        archive.CreateEntryFromAny(sourcePath);
    }
}
于 2018-07-25T08:43:49.490 回答
9

如果您正在开发一个可以使用完整 .NET 的项目,您可以尝试使用 ZipFile.CreateFromDirectory 方法,如下所述

using System;
using System.IO;
using System.IO.Compression;

namespace ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            string startPath = @"c:\example\start";
            string zipPath = @"c:\example\result.zip";
            string extractPath = @"c:\example\extract";

            ZipFile.CreateFromDirectory(startPath, zipPath, CompressionLevel.Fastest, true);

            ZipFile.ExtractToDirectory(zipPath, extractPath);
        }
    }
}

当然,这仅在您基于给定目录创建新 Zip 时才有效。

根据评论,以前的解决方案不保留目录结构。如果需要,那么下面的代码可能会解决这个问题:

    var InputDirectory = @"c:\example\start";
    var OutputFilename = @"c:\example\result.zip";
    using (Stream zipStream = new FileStream(Path.GetFullPath(OutputFilename), FileMode.Create, FileAccess.Write))
    using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create))
    {
        foreach(var filePath in System.IO.Directory.GetFiles(InputDirectory,"*.*",System.IO.SearchOption.AllDirectories))
        {
            var relativePath = filePath.Replace(InputDirectory,string.Empty);
            using (Stream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
            using (Stream fileStreamInZip = archive.CreateEntry(relativePath).Open())
                fileStream.CopyTo(fileStreamInZip);
        }
    }
于 2014-10-21T16:19:30.597 回答
7

这是一种可能的解决方案:

public static class ZipArchiveExtension
{
    public static ZipArchiveDirectory CreateDirectory(this ZipArchive @this, string directoryPath)
    {
        return new ZipArchiveDirectory(@this, directoryPath);
    }
}

public class ZipArchiveDirectory
{
    private readonly string _directory;
    private ZipArchive _archive;

    internal ZipArchiveDirectory(ZipArchive archive, string directory)
    {
        _archive = archive;
        _directory = directory;
    }

    public ZipArchive Archive { get{return _archive;}}

    public ZipArchiveEntry CreateEntry(string entry)
    {
        return _archive.CreateEntry(_directory + "/" + entry);
    }

    public ZipArchiveEntry CreateEntry(string entry, CompressionLevel compressionLevel)
    {
        return _archive.CreateEntry(_directory + "/" + entry, compressionLevel);
    }
}

并使用:

var directory = _archive.CreateDirectory(context);
var entry = directory.CreateEntry(context);
var stream = entry.Open();

但我可以预见嵌套问题,也许。

于 2013-02-28T11:15:54.830 回答
3

我也在寻找类似的解决方案,发现@Val 和@sDima 的解决方案对我来说更有希望。但我发现代码存在一些问题,并修复了它们以与我的代码一起使用。

和@sDima 一样,我也决定使用 Extension 为 ZipArchive 添加更多功能。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.Compression;
using System.IO;

namespace ZipTest
{
   public static class ZipArchiveExtensions
   {
      public static void CreateEntryFromAny(this ZipArchive archive, string sourceName, string entryName, CompressionLevel compressionLevel = CompressionLevel.Optimal)
    {
        try
        {
            if (File.GetAttributes(sourceName).HasFlag(FileAttributes.Directory))
            {
                archive.CreateEntryFromDirectory(sourceName, entryName, compressionLevel);
            }
            else
            {
                archive.CreateEntryFromFile(sourceName, entryName, compressionLevel);
            }
        }
        catch
        {
            throw;
        }
    }

    public static void CreateEntryFromDirectory(this ZipArchive archive, string sourceDirName, string entryName, CompressionLevel compressionLevel)
    {
        try
        {
            var files = Directory.EnumerateFileSystemEntries(sourceDirName);
            if (files.Any())
            {
                foreach (var file in files)
                {
                    var fileName = Path.GetFileName(file);
                    archive.CreateEntryFromAny(file, Path.Combine(entryName, fileName), compressionLevel);
                }
            }
            else
            {
                //Do a folder entry check.
                if (!string.IsNullOrEmpty(entryName) && entryName[entryName.Length - 1] != '/')
                {
                    entryName += "/";
                }

                archive.CreateEntry(entryName, compressionLevel);
            }
        }
        catch
        {
            throw;
        }
      }
    }
  }

您可以使用下面给出的简单方法尝试扩展,

 class Program
 {
    static void Main(string[] args)
    {
        string filePath = @"C:\Users\WinUser\Downloads\Test.zip";
        string dirName = Path.GetDirectoryName(filePath);
       
        if (File.Exists(filePath))
            File.Delete(filePath);

        using (ZipArchive archive = ZipFile.Open(filePath, ZipArchiveMode.Create))
        {
            archive.CreateEntryFromFile( @"C:\Users\WinUser\Downloads\file1.jpg", "SomeFolder/file1.jpg", CompressionLevel.Optimal);
            archive.CreateEntryFromDirectory(@"C:\Users\WinUser\Downloads\MyDocs", "OfficeDocs", CompressionLevel.Optimal);
            archive.CreateEntryFromAny(@"C:\Users\WinUser\Downloads\EmptyFolder", "EmptyFolder", CompressionLevel.Optimal);
        };

        using (ZipArchive zip = ZipFile.OpenRead(filePath))
        {
            string dirExtract = @"C:\Users\WinUser\Downloads\Temp";
            if (Directory.Exists(dirExtract))
            {
                Directory.Delete(dirExtract, true);
            }

            zip.ExtractToDirectory(dirExtract);
        }
     }
   }

我试图从 .Net 框架中为扩展方法保留标准 CreateEntryFromFile 的确切行为。

要使用给出的示例代码,请添加对 System.IO.Compression.dll 和 System.IO.Compression.FileSystem.dll 的引用。

以下是这个扩展类的优点

  1. 递归添加文件夹内容。
  2. 支持空文件夹。
于 2020-04-30T06:09:59.047 回答
2

我的回答是基于 Val 的回答,但在性能方面有所改进,并且不会在 ZIP 中生成空文件。

public static class ZipArchiveExtensions
{
    public static void CreateEntryFromAny(this ZipArchive archive, string sourceName, string entryName = "")
    {
        var fileName = Path.GetFileName(sourceName);
        if (File.GetAttributes(sourceName).HasFlag(FileAttributes.Directory))
        {
            archive.CreateEntryFromDirectory(sourceName, Path.Combine(entryName, fileName));
        }
        else
        {
            archive.CreateEntryFromFile(sourceName, Path.Combine(entryName, fileName), CompressionLevel.Optimal);
        }
    }

    public static void CreateEntryFromDirectory(this ZipArchive archive, string sourceDirName, string entryName = "")
    {
        var files = Directory.EnumerateFileSystemEntries(sourceDirName);
        foreach (var file in files)
        {
            archive.CreateEntryFromAny(file, entryName);
        }
    }
}

使用示例:

// Create and open a new ZIP file
                using (var zip = ZipFile.Open(ZipPath, ZipArchiveMode.Create))
                {
                    foreach (string file in FILES_LIST)
                    {
                        // Add the entry for each file
                        zip.CreateEntryFromAny(file);
                    }
                }
于 2020-04-13T17:38:12.993 回答
2

一个更优化的ZipArchive扩展,它添加了文件夹结构,包括所有子文件夹和文件到 zip。已解决IOException进程无法访问文件...),如果文件在压缩时刻正在使用,则抛出该文件,例如某些记录器

public static class ZipArchiveExtensions
{
    public static void AddDirectory(this ZipArchive @this, string path)
    {
        @this.AddDirectory(path, string.Empty);
    }

    private static void AddDirectory(this ZipArchive @this, string path, string relativePath)
    {
        var fileSystemEntries = Directory.EnumerateFileSystemEntries(path);

        foreach (var fileSystemEntry in fileSystemEntries)
        {
            if (File.GetAttributes(fileSystemEntry).HasFlag(FileAttributes.Directory))
            {
                @this.AddDirectory(fileSystemEntry, Path.Combine(relativePath, Path.GetFileName(fileSystemEntry)));
                continue;
            }

            var fileEntry = @this.CreateEntry(Path.Combine(relativePath, Path.GetFileName(fileSystemEntry)));

            using (var zipStream = fileEntry.Open())
            using (var fileStream = new FileStream(fileSystemEntry, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            using (var memoryStream = new MemoryStream())
            {
                fileStream.CopyTo(memoryStream);

                var bytes = memoryStream.ToArray();
                zipStream.Write(bytes, 0, bytes.Length);
            }
        }
    }
}
于 2021-01-12T14:15:36.010 回答
2

@Andrey 的非常好的方法有一点变化

public static void CreateEntryFromDirectory2(this ZipArchive archive, string sourceDirName, CompressionLevel compressionLevel = CompressionLevel.Fastest)
    {
        var folders = new Stack<string>();

        folders.Push(sourceDirName);

        do
        {
            var currentFolder = folders.Pop();

            foreach (var item in Directory.GetFiles(currentFolder))
            {
                archive.CreateEntryFromFile(item, item.Substring(sourceDirName.Length + 1), compressionLevel);
            }

            foreach (var item in Directory.GetDirectories(currentFolder))
            {
                folders.Push(item);
            }
        } 
        while (folders.Count > 0);
    }
于 2021-02-13T22:36:32.387 回答
1

对带有子文件夹的 Zip 文件夹使用递归方法。

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;

public static async Task<bool> ZipFileHelper(IFolder folderForZipping, IFolder folderForZipFile, string zipFileName)
{
    if (folderForZipping == null || folderForZipFile == null
        || string.IsNullOrEmpty(zipFileName))
    {
        throw new ArgumentException("Invalid argument...");
    }

    IFile zipFile = await folderForZipFile.CreateFileAsync(zipFileName, CreationCollisionOption.ReplaceExisting);

    // Create zip archive to access compressed files in memory stream
    using (MemoryStream zipStream = new MemoryStream())
    {
        using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
        {
            await ZipSubFolders(folderForZipping, zip, "");
        }

        zipStream.Position = 0;
        using (Stream s = await zipFile.OpenAsync(FileAccess.ReadAndWrite))
        {
            zipStream.CopyTo(s);
        }
    }
    return true;
}

//Create zip file entry for folder and subfolders("sub/1.txt")
private static async Task ZipSubFolders(IFolder folder, ZipArchive zip, string dir)
{
    if (folder == null || zip == null)
        return;

    var files = await folder.GetFilesAsync();
    var en = files.GetEnumerator();
    while (en.MoveNext())
    {
        var file = en.Current;
        var entry = zip.CreateEntryFromFile(file.Path, dir + file.Name);                
    }

    var folders = await folder.GetFoldersAsync();
    var fEn = folders.GetEnumerator();
    while (fEn.MoveNext())
    {
        await ZipSubFolders(fEn.Current, zip, dir + fEn.Current.Name + "/");
    }
}
于 2018-01-25T20:02:36.867 回答
1

我不喜欢@Val、@sDima、@Nitheesh 提出的递归。由于堆栈的大小有限,它可能会导致StackOverflowException 。所以这是我的两分钱树遍历。

public static class ZipArchiveExtensions
{
    public static void CreateEntryFromDirectory(this ZipArchive archive, string sourceDirName, CompressionLevel compressionLevel = CompressionLevel.Fastest)
    {
        var folders = new Stack<string>();
        folders.Push(sourceDirName);
        
        do
        {
            var currentFolder = folders.Pop();
            Directory.GetFiles(currentFolder).ForEach(f => archive.CreateEntryFromFile(f, f.Substring(sourceDirName.Length+1), compressionLevel));
            Directory.GetDirectories(currentFolder).ForEach(d => folders.Push(d));
        } while (folders.Count > 0);
    }
}
于 2020-07-08T14:57:23.323 回答
0

它对我有用。

静态类

public static class ZipArchiveExtension
{

    public static void CreateEntryFromAny(this ZipArchive archive, string sourceName, string entryName = "")
    {
        var fileName = Path.GetFileName(sourceName);            
        if (File.GetAttributes(sourceName).HasFlag(FileAttributes.Directory))
        {
            archive.CreateEntryFromDirectory(sourceName, Path.Combine(entryName, fileName));
        }
        else
        {
            archive.CreateEntryFromFile(sourceName, Path.Combine(entryName, fileName), CompressionLevel.Fastest);
        }
    }

    public static void CreateEntryFromDirectory(this ZipArchive archive, string sourceDirName, string entryName = "")
    {
        string[] files = Directory.GetFiles(sourceDirName).Concat(Directory.GetDirectories(sourceDirName)).ToArray();

        if (files.Any())
        {

            foreach (var file in files)
            {
                archive.CreateEntryFromAny(file, entryName);
            }
        }
        else
        {
            if (!string.IsNullOrEmpty(entryName) && entryName[entryName.Length - 1] != '/')
            {
                entryName += "\\";
            }
            archive.CreateEntry(entryName);
        }
        
    }
}

像这样调用方法

            byte[] archiveFile;

            using (var memoryStream = new MemoryStream())
            {
                using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
                {
                        archive.CreateEntryFromAny(file.Path);

                }
                archiveFile = memoryStream.ToArray();
            }
于 2021-12-08T05:34:43.847 回答