65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 java.util.zip .NET 命名空间和其他功能进行 Zip/Unzip

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.49/5 (15投票s)

2007 年 2 月 20 日

CPOL

5分钟阅读

viewsIcon

167597

downloadIcon

3099

使用托管代码中的 java.util.zip 进行 Zip/Unzip。

Screenshot - zipstrip2.png

目录

  • 引言
  • 背景
  • 列出 Zip 文件的内容
  • 从文件夹 Zip 文件
  • 从 Zip 文件 Unzip 文件
  • 更改 Zip 文件
  • 版本 1.0 的改进
  • 使用通配符进行过滤
  • Explorer 文件夹上下文菜单
  • 历史

    引言

    在处理 Zip 文件时,您有几种选择:使用第三方 DLL 的原生 API、Java API 或 .NET API。如果您急于使用 System.IO.Compress .NET 命名空间 中的 API,您会非常失望。出于微软自己也不知道的原因,其支持仅限于流,完全不支持多文件归档。这可能是第三方 .NET 库(如 SharpZipLib)出现的原因。如果您不信任免费软件,您可能会惊讶地发现,您可以在 J# 程序集中的 .NET 中找到对多文件归档的支持,这些程序集与 Java API 具有同等功能。为了制作一个有用的应用程序,我从一个 现有的 CodeProject 应用程序 开始,该应用程序在备份源代码时非常方便。我替换了 SharpZipLib 引用,转而使用了 Microsoft J# API。在移植应用程序时,我注意到 SharpZipLib API 与 J# API 非常相似,这使我的工作变得容易得多。为了使此实用程序更具吸引力,我添加了许多功能,我将在下面详细介绍。

    背景

    为了使用 Microsoft 的多文件 Zip 和 Java 流 API,您必须将 *vjslib.dll* 和 *vjslibcw.dll* .NET 程序集添加为项目引用。它们是 J# 发行包 的一部分。Java 类似的类型将出现在 `java.util.zip` 命名空间中。由于 Microsoft 在此主题上的文档非常稀疏,我经常不得不依赖 IntelliSense 来弄清楚。为了简单起见,以下代码省略了一些非必要的 UI 代码,这些代码仅在提供的源代码中可以找到。

    列出 Zip 文件的内容

    下面,您可以看到一段为简化而编辑的代码片段,用于枚举存档中的文件

    public static List<string > GetZipFileNames(string zipFile)
    {
        ZipFile zf = null;
        List<string > list = new List<string >();
        try
        {
            zf = new ZipFile(zipFile);
            java.util.Enumeration enu = zf.entries();
            while (enu.hasMoreElements())
            {
                ZipEntry zen = enu.nextElement() as ZipEntry;
                if (zen.isDirectory())
                    continue;//ignore directories
                list.Add(zen.getName());
            }
    
        }
        catch(Exception ex) 
        {
            throw new ApplicationException("Please drag/drop only valid " 
                      "zip files\nthat are not password protected.",ex);
        }
        finally
        {
            if (zf != null)
                zf.close();               
        }
        return list;
    }

    您可能已经注意到,`ZipEntry` 和 `ZipFile` 对此目标来说易于使用。

    从文件夹 Zip 文件

    下面,您可以看到一个用于从文件夹 Zip 文件的辅助方法

    private static void _CreateZipFromFolder(string Folder, IsFileStrippableDelegate IsStrip)
    {
        System.IO.DirectoryInfo dirInfo =
            new System.IO.DirectoryInfo(Folder);
        System.IO.FileInfo[] files = dirInfo.GetFiles("*");//all files
        foreach (FileInfo file in files)
        {
            if (IsStrip != null && IsStrip(file.FullName))
                continue;//skip, don't zip it
            java.io.FileInputStream instream = new java.io.FileInputStream(file.FullName);
            int bytes = 0;
            string strEntry = file.FullName.Substring(m_trimIndex);
            _zos.putNextEntry(new ZipEntry(strEntry));
            while ((bytes = instream.read(_buffer, 0, _buffer.Length)) > 0)
            {
                _zos.write(_buffer, 0, bytes);
            }
            _zos.closeEntry();
            instream.close();
        }
    
        System.IO.DirectoryInfo[] folders = null;
        folders = dirInfo.GetDirectories("*");
        if (folders != null)
        {
            foreach (System.IO.DirectoryInfo folder in folders)
            {
                _CreateZipFromFolder(folder.FullName, IsStrip);
            }
        }
    }

    `IsStrip` 委托充当一个过滤器,用于丢弃不需要的文件。

    从 Zip 文件 Unzip 文件

    下面,您可以看到一段为简洁而编辑的代码,用于从 Zip 中 Unzip 文件

    ZipInputStream zis = null;
    zis = new ZipInputStream(new java.io.FileInputStream(file));
    ZipEntry ze = null;
    while ((ze = zis.getNextEntry()) != null)
    {
        if (ze.isDirectory())
            continue;//ignore directories
        string fname = ze.getName();
        bool bstrip = IsStrip != null && IsStrip(fname);
        if (!bstrip)
        {
            //unzip entry
            int bytes = 0;
            FileStream filestream = null;
            BinaryWriter w = null;
            string filePath = Folder + @"\" + fname;
            if(!Directory.Exists(Path.GetDirectoryName(filePath)))
                Directory.CreateDirectory(Path.GetDirectoryName(filePath));
            filestream = new FileStream(filePath, FileMode.Create);
            w = new BinaryWriter(filestream);
            while ((bytes = zis.read(_buffer, 0, _buffer.Length)) > 0)
            {
                for (int i = 0; i < bytes; i++)
                {
                    unchecked
                    {
                        w.Write((byte)_buffer[i]);
                    }
                }
            }
        }
        zis.closeEntry();
        w.Close();
        filestream.Close();
    
        }
        if (zis != null)
            zis.close();
    }

    同样,`IsStrip` 委托充当一个过滤器,用于丢弃不需要的文件。另外,由于 `sbyte[]` 数组,我不得不混合使用 `java.io` 和 `System.IO` 命名空间。

    更改 Zip 文件

    您不能直接修改 Zip 文件。但是,您可以创建一个新的 Zip 文件,只复制选定的文件。传输完成后,我们可以将新文件重命名为原始文件,这样看起来就像我们更改了 Zip 文件。下面这段为简洁而编辑的方法接收一个包含不需要文件的字符串列表

    public static void StripZip(string zipFile, List<string > trashFiles)
    {
        ZipOutputStream zos = null;
        ZipInputStream zis = null;
        //remove 'zip' extension
        bool bsuccess = true;
        string strNewFile = zipFile.Remove(zipFile.Length - 3, 3) + "tmp";
        zos = new ZipOutputStream(new java.io.FileOutputStream(strNewFile));
        zis = new ZipInputStream(new java.io.FileInputStream(zipFile));
        ZipEntry ze = null;
        while ((ze = zis.getNextEntry()) != null)
        {
            if (ze.isDirectory())
                continue;//ignore directories
            string fname = ze.getName();
            bool bstrip = trashFiles.Contains(fname);
            if (!bstrip)
            {
                //copy the entry from zis to zos
                int bytes = 0;
                //deal with password protected files
                zos.putNextEntry(new ZipEntry(fname));
                while ((bytes = zis.read(_buffer, 0, _buffer.Length)) > 0)
                {
                    zos.write(_buffer, 0, bytes);
                }
                zis.closeEntry();
                zos.closeEntry();
            }
        }
        if (zis != null)
            zis.close();
        if (zos != null)
            zos.close();
        if (bsuccess)
        {
            System.IO.File.Delete(zipFile + ".old");
            System.IO.File.Move(zipFile, zipFile + ".old");
            System.IO.File.Move(strNewFile, zipFile);
        }
        else
            System.IO.File.Delete(strNewFile);
    }

    版本 1.0 的改进

    为了使该工具更具吸引力,我添加了一些我自己的改进:第一个值得注意的是使用了检查列表框,它允许即时手动更改。我最喜欢的是能够通过 `DataTable` 编辑绑定到 *CPZipStripper.exe.xml* 文件的过滤器扩展列表。这是该文件的一个编辑快照

    <configuration>
      <maskRow maskField="*.plg" / >
      <maskRow maskField=".opt" / >
      <maskRow maskField=".ncb" / >
      <maskRow maskField=".suo" / >
      <maskRow maskField="*.pdb" / >
    ......
    </configuration>

    从配置文件读取数据

    请注意,在应用程序配置文件中,我们不仅保留了 `appSetttings` 节点,还保留了文件、路径,最重要的是 `DataTable` 内容。从该 XML 文件加载数据到相应的列表和 `DataSet` 中非常容易

    XmlDocument xd = new XmlDocument();
    xd.Load(cfgxmlpath);
    //use plain xml xpath for the rest
    m_paths.Clear();
    XmlNode xnpath = xd["configuration"]["paths"];
    if(xnpath!=null)
    {
        foreach(XmlNode xn in xnpath.ChildNodes)
        {
            m_paths.Add(xn.InnerXml);                        
        }    
    }
    XmlNode xnfile = xd["configuration"]["files"];
    if(xnfile!=null)
    {
        foreach(XmlNode xn in xnfile.ChildNodes)
        {
            m_files.Add(xn.InnerXml);                        
        }    
    }
    //use the data set
    m_extensions.Clear();
    _dataSet = new DataSet("configuration");
    DataTable mytable = new DataTable("maskRow");
    DataColumn exColumn = new DataColumn("maskField", 
        Type.GetType("System.String"), null, MappingType.Attribute);
    mytable.Columns.Add(exColumn);
    _dataSet.Tables.Add(mytable);
    _dataSet.Tables[0].ReadXml(MainForm.cfgxmlpath);
    for (int i = 0; i < _dataSet.Tables[0].Rows.Count; i++)
    {
        DataRow row = _dataSet.Tables[0].Rows[i];
        string val = row[0].ToString().ToLower();
        if (val.Length > 0)//no empty mask
        {
        //.....code eliminated for brevity
        }
        else
        {   //don't show empty rows
            row.Delete();
        }
    }
    _dataSet.Tables[0].AcceptChanges();

    将数据写入配置文件

    使用 `DataSet` 的 `WriteXml` 将删除不属于该表的数据。因此,我们必须在调用 `WriteXml` 之前将其保存,并在之后恢复它

    XmlDocument xd = new XmlDocument();
    //get the original
    xd.Load(MainForm.cfgxmlpath);
    //save nodes not part of the dataset
    XmlNode xnpath = xd["configuration"]["paths"];
    XmlNode xnfile = xd["configuration"]["files"];
    XmlNode lastFolderpath = xd["configuration"]["LastUsedFolder"];
    
    //write the masks
    _dataSet.WriteXml(MainForm.cfgxmlpath);
    
    //restore the old saved nodes
    xd.Load(MainForm.cfgxmlpath);
    if(xnpath != null)
        xd.DocumentElement.AppendChild(xnpath);
    if (xnfile != null)
         xd.DocumentElement.AppendChild(xnfile);
    if (lastFolderpath != null)
        xd.DocumentElement.AppendChild(lastFolderpath);
    xd.Save(MainForm.cfgxmlpath);

    使用通配符进行过滤

    我不会在这里详细介绍,但正如您在 XML 片段中已经注意到的,您可以使用 * 和 ? 字符。打开此应用程序后,您首先要做的就是设置配置,这是一个好主意。

    Screenshot - config.png

    Explorer 文件夹上下文菜单

    我添加了一些关于使用 Explorer 上下文菜单的新功能。您应该只启动一次 exe,然后就可以右键单击文件夹并对其进行 Zip 操作。

    Screenshot - Menu.png

    历史

    作为一个实用工具,我认为 2.x 版本比旧版本有所改进。在一定程度上,您可以将其用作 WinZip 的替代品,但它缺少加密等功能。必须在您的机器上安装 .NET 2.0 和 J# 包才能运行它。如果您在单独运行 exe 时遇到问题,可能是因为您缺少 J# 发行包或 .NET 2.0 运行时。如果确实如此,我建议您尝试安装 我创建的这个 MSI 安装文件 或下载 *vjredist_32bit.zip* 并本地安装。

    版本 2.3

    我使用 Outlook 包装器为应用程序添加了电子邮件功能,并做了一些小的增强。我还包含了创建 MSI 文件的部署项目。

    2.4 版

    我强制程序集以 32 位模式运行以避免一些 64 位问题,并为安装程序添加了一些自定义操作。

© . All rights reserved.