【问题标题】:Creating Directories in a ZipArchive C# .Net 4.5在 ZipArchive C# .Net 4.5 中创建目录
【发布时间】:2013-02-14 12:45:00
【问题描述】:

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

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

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

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

而这些方面的东西对我来说似乎更好:

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

【问题讨论】:

  • 您是指单个 zip 中的文件夹结构,还是 zip 的层次结构?
  • 单个 zip 中的文件夹结构。

标签: c# .net zip .net-4.5


【解决方案1】:

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

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();

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

【讨论】:

    【解决方案2】:

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

    using (var fs = new FileStream("1.zip", FileMode.Create))
    using (var zip = new ZipArchive(fs, ZipArchiveMode.Create))
    {
        zip.CreateEntry("12/3/"); // just end with "/"
    }
    

    【讨论】:

      【解决方案3】:

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

      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);
              }
          }
      

      【讨论】:

      • 只是一个迂腐的笔记,我在一些摆弄后发现的。它不像 Windows 桌面上的标准“发送到->压缩文件”那样完全工作。 Windows SendTo 将为结构中的目录显式创建条目,因为此方法隐式维护目录结构,即它不为目录创建条目,但目录列在每个文件的完整路径中。它不会改变 winzip 函数的工作方式(它很高兴与任何一个一起工作),但如果您期望一个特定的文件结构,这只是一个了解的问题。
      • 谢谢。节省了我很多时间。我只是对代码做了一个小的修改,希望你能接受。我添加了 Substring(1) 以获取相对路径,以使 zip 文件可浏览。
      【解决方案4】:

      对带有子文件夹的 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 + "/");
          }
      }
      

      【讨论】:

        【解决方案5】:

        我知道我参加聚会迟到了(2018 年 7 月 25 日),

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

        首先,记得安装 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);
            }
        }
        

        【讨论】:

        • 感谢分享,为我节省了一些时间
        • 这是一个救命的答案!
        • 我赞成该算法,尽管它无法编译。 foreach 定义了 file 并使用 fileName 代替。
        • @kpull1 谢谢!这是由编辑建议引起的错误。固定
        • 当我删除该行时,这对我有用:archive.CreateEntry(Path.Combine(entryName, Path.GetFileName(sourceDirName))); //这行好像多加了一个与文件夹名同名的空文件。
        【解决方案6】:

        我的回答是基于 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);
                            }
                        }
        

        【讨论】:

          【解决方案7】:

          我也在寻找类似的解决方案,发现@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. 支持空文件夹。

          【讨论】:

            【解决方案8】:

            我不喜欢 @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);
                }
            }
            

            【讨论】:

            • 非常好的解决方案!我已将 Array.ForEach 更改为常规 foreach
            【解决方案9】:

            一个更优化的ZipArchive 扩展,它添加了文件夹结构,包括所有子文件夹和压缩文件。解决了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);
                        }
                    }
                }
            }
            

            【讨论】:

            • 我在我的代码中将第二种方法更改为公开。我还删除了内存流,因为我不想将整个文件读入 RAM:fileStream.CopyTo(zipStream);
            【解决方案10】:

            @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);
                }
            

            【讨论】:

              【解决方案11】:

              它对我有用。

              静态类

              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();
                          }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2015-09-05
                • 2012-09-29
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多