在ZipArchive c# . net 4.5中创建目录
本文关键字:创建目录 net ZipArchive | 更新日期: 2023-09-27 18:01:44
ZipArchive是ZipArchiveEntries的集合,添加/删除"Entries"工作得很好。但似乎没有目录/嵌套"档案"的概念。理论上,该类与文件系统解耦,因为您可以完全在内存流中创建归档。但是,如果希望在归档文件中添加目录结构,则必须在条目名称前加上路径。
问题:你将如何扩展ZipArchive来创建一个更好的创建和管理目录的界面?
例如,当前将文件添加到目录的方法是使用目录路径创建条目:
var entry = _archive.CreateEntry("directory/entryname");
而我觉得下面这几行比较好:
var directory = _archive.CreateDirectoryEntry("directory");
var entry = _directory.CreateEntry("entryname");
您可以使用如下方式,换句话说,手动创建目录结构:
using (var fs = new FileStream("1.zip", FileMode.Create))
using (var zip = new ZipArchive(fs, ZipArchiveMode.Create))
{
zip.CreateEntry("12/3/"); // just end with "/"
}
我知道我迟到了(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();
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);
}
}
如果你正在做一个项目,可以使用完整的。net,你可以尝试使用ZipFile。方法,如下所示:
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);
}
}
这里有一个可能的解决方案:
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);
}
}
和used:
var directory = _archive.CreateDirectory(context);
var entry = directory.CreateEntry(context);
var stream = entry.Open();
但是我可以预见到嵌套的问题。
我也在寻找一个类似的解决方案,发现@Val &@ dima的解决方案对我来说更有希望。但是我发现了代码中的一些问题,并将它们修复以与我的代码一起使用。
像@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 . filessystem .dll的引用。
这个扩展类
的优点如下- 递归添加文件夹内容。
- 支持空文件夹
来自@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);
}
我的答案是基于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);
}
}
一个更调谐的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);
}
}
}
}
使用递归方法对带有子文件夹的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 + "/");
}
}
我不喜欢递归,因为它是由@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);
}
}
这对我来说很有效。
静态类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();
}