AI写作智能体 自主规划任务,支持联网查询和网页读取,多模态高效创作各类分析报告、商业计划、营销方案、教学内容等。 广告
[TOC] # Zip文件的解压与压缩 ## 简介 利用 Java 将 Zip 进行解压,以及将一个文件夹或文件压缩为 Zip 。 ## 关键的类以及它们的常用方法 * **[ZipFile]** 代表一个 Zip * `new ZipFile(new File("Zip的路径"))` * `getEntry(String name)` * `getInputStream(ZipEntry entry)` * `entries()` * **[ZipEntry]** 代表 Zip 中的文件 * `isDirectory()` * **[ZipOutputStream]** 压缩流的输出流,帮助输出 Zip * `putNextEntry(ZipEntry e)` * `setComment(String comment)` ## 将单个文件压缩为Zip ~~~ public static void demo01() throws Exception { //定位到一个存在的"hello.txt"文件 File file = new File("C:" + File.separator + "test" + File.separator + "hello.txt"); //这个File的路径就是Zip生产的路径,目前这个Zip是不存在的 File zipFile = new File("C:" + File.separator + "test" + File.separator + "hello.zip"); //输入流读取数据,所以是从txt文件中读取 FileInputStream input = new FileInputStream(file); //输出流输出数据,输出Zip ZipOutputStream output = new ZipOutputStream(new FileOutputStream(zipFile)); //用于缓存数据 int temp = 0; //用于生成说明,"test"会位于打开Zip后,右边的区域 output.setComment("test"); //从输入流中获取的数据不能直接写入Zip,而是需要在Zip中新建一个 Entry,然后将数据写入新建的才对,这里就是在Zip中新建了一个名为"hello.txt"的文件 output.putNextEntry(new ZipEntry(file.getName())); //下面的代码大家应该非常熟悉了 while ((temp = input.read()) != -1) { output.write(temp); } if (input != null) { input.close(); } if (output != null) { output.close(); } } ~~~ 这里出现了 `putNextEntry(ZipEntry e)` 方法,根据文档的描述,它可以在 Zip 中新建一个 entry (类似是一个文件或文件夹),并且将流定位到这个文件的开头,这也解释了表面上我们是 `output.write` ,但由于这个输出流是 Zip 输出流,所以数据是写到了 Zip 中的 ZipEntry 中,另外根据它的源码 ~~~ public void putNextEntry(ZipEntry var1) throws IOException { this.ensureOpen();//暂时忽略这行代码,主要看下面的 if 语句 if(this.current != null) { this.closeEntry(); } ........................省略一大部分代码 this.current = new ZipOutputStream.XEntry(var1, this.written); ~~~ 在调用的时候会对自己的 current 变量进行判断,那么 `closeEntry()` 方法是什么作用呢,根据文档的描述:关闭当前的 ZipEntry ,并将流定位到下个 entry。注意,这里的 `closeEntry()` 并没有对 ZipOutputStream 进行影响,ZipOutputStream 仍然处于未关闭状态,那么 `putNextEntry()` 为什么要调用 `closeEntry()` 呢?其实非常有用,这里用代码来说明: ~~~ //根据上面的例子,我们生成了一个 "hello.zip",里面有一个 "hello.txt",现在需要改变一下,我们要将 2 个文件,压缩到 "hello.zip" 中,分别是 "hello.txt","helloWorld.txt" //同时假设 putNextEntry 没有写 closeEntry 的方法 public static void demo01() throws Exception { File base = new File("C:" + File.separator + "test"); File hello = new File(base, "hello.txt"); File helloWorld = new File(base, "helloWorld.txt"); InputStream input = null; ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(new File(base, "hello.zip"))); zipOut.putNextEntry(new ZipEntry("hello.txt")); input = new FileInputStream(hello); int temp = 0; while ((temp = input.read()) != -1) { zipOut.write(temp); } //关闭当前的 entry ,因为我们还想写入下一个文件 if (zipOut != null) { zipOut.closeEntry(); } if (input != null) { input.close(); } temp = 0; input = new FileInputStream(helloWorld); zipOut.putNextEntry(new ZipEntry("helloWorld.txt")); while ((temp = input.read()) != -1) { zipOut.write(temp); } if (input != null) { input.close(); } if (zipOut != null) { zipOut.closeEntry(); zipOut.close(); } } ~~~ 看完这段代码的话,我想大家应该是比较清楚了,如果一个 entry 读取完毕,我们就需要调用 `closeEntry()` 方法的话,是非常不方便的,因为一个 Zip 中文件的数量会很多,所以对于多个文件,我们再读取完一个 entry 后,可以直接调用 `putNextEntry` 方法,因为它会帮我们把当前的 entry 进行 `close` 操作。不过话说回来,最后的最后我们还是得调用一下 `closeEntry` ,可以通过简化上述的代码,加深理解。 ~~~ public static void demo01() throws Exception { File base = new File("C:" + File.separator + "Program Files" + File.separator + "test"); File file = new File(base, "hi"); ZipOutputStream zipOut = new ZipOutputStream( new FileOutputStream(new File(base, "hi.zip"))); InputStream input = null; if (!file.isDirectory()) { return; } int temp = 0; for (File entry : file.listFiles()) { input = new FileInputStream(entry); // ZipEntry 和 File 在构建的时候有些相似,通过这行代码,我们可以在 Zip 文件中生成一个同名的文件夹,与我们平时使用当中的情境差不多: hi.zip 有一个 hi 文件夹,hi 文件夹中有 hello.txt 和 helloWorld.txt zipOut.putNextEntry(new ZipEntry(file.getName() + File.separator + entry.getName())); while ((temp = input.read()) != -1) { zipOut.write(temp); } temp = 0; input.close(); } zipOut.closeEntry(); zipOut.close(); } ~~~ ## 压缩复杂的Zip 压缩一,两个文件显然是无法满足大多数情况的,我们更需要的是一种“万能”的压缩工具类,即:我就告诉你我想要压缩的文件或者文件夹在哪,叫什么名字,你直接帮我压缩就好。这里为大家演示一种采用**递归**形式的,可以实现这一效果。 首先看一下我们要压缩的文件夹: ![](https://box.kancloud.cn/657d4cdfc0fecea5f54849d3333162ae_704x224.png) ~~~ private ZipOutputStream zipOut; public static void main(String[] args) throws Exception { FileUtils fileUtils = new FileUtils(); File file = new File("C:" + File.separator + "test" + File.separator + "3"); File zipFile = new File("C:" + File.separator + "test" + File.separator + "hello.zip"); fileUtils.zipOut = new ZipOutputStream(new FileOutputStream(zipFile)); fileUtils.compression(file); } public void compression(File file) throws Exception { if (file.isFile()) { compressionFile(file, ""); } else { compressionDirectory(file, file.getName()); } zipOut.closeEntry(); zipOut.close(); System.out.println("Done"); } public void compressionFile(File file, String baseDir) throws Exception { FileInputStream fileInputStream = new FileInputStream(file); int temp = 0; zipOut.putNextEntry(new ZipEntry(baseDir + file.getName())); while ((temp = fileInputStream.read()) != -1) { zipOut.write(temp); } fileInputStream.close(); } public void compressionDirectory(File file, String baseDir) throws Exception { File[] fileArray = file.listFiles(); if (fileArray == null || fileArray.length == 0) { zipOut.putNextEntry(new ZipEntry(baseDir + File.separator)); zipOut.closeEntry(); return; } for (File entry : fileArray) { checkFileOrDirectory(entry, baseDir + File.separator); } } public void checkFileOrDirectory(File entry, String baseDir) throws Exception { if (entry.isFile()) { compressionFile(entry, baseDir); } else { compressionDirectory(entry, baseDir + entry.getName()); } } ~~~ 这里的代码需要详细说明一下,我们按照执行顺序来: 1. **`main()`**:`file` 是我们要进行压缩的文件夹,`zipFile` 是我们想要输出的 Zip 文件,输出 Zip 文件需要一个 `ZipOutputStream`,因为之后要用到,所以声明为全局变量,此时调用 `compression()` 2. **`compression()`**:主要判断传入的文件是文件还是文件夹,这里我们传入的是文件夹,所以调用 `compressDirectory()` 3. **`compressDirectory()`**:我们接收到了 `file`,目前的 `baseDir` 是 3,获得 `file` 目录下的文件数组,当前 `!=null`,所以我们忽略 `if`,开始遍历每个文件,假设目前我们的 `entry` 是 `C:/test/3/5` 调用 `checkFileOrDirectory()` 方法。 4. **`checkFileOrDirectory()`**:接收到 `C:/test/3/5` 和 `3/` 因为我们当前的 `entry` 是个文件夹,所以我们又调用 `compressionDirectory()`,将 `C:/test/3/5` 这个 `File` 和 `3/5` 传入。 5. **`compressDirectory()`**:获得 `C:/test/3/5` 这个文件夹下的文件,目前 `!=null`,所以我们跳过 `if`,遍历文件,目前文件只有一个 `C:/test/3/5/6.txt`,调用 `checkFileOrDirectory()`。 6. **`checkFileOrDirectory()`**:因为是文件,所以调用 `compresstionFile()` 方法,将 `C:/test/3/5/6.txt` 和 `3/5/` 传入。 7. **`compresstionFile()`**:此时的 `baseDir` 是 `3/5/`,`file.getName()` 是 `6.txt`,所以我们通过 `putNextEntry(new ZipEntry("3/5/6.txt"))`,这样我们 Zip 的第一个 `ZipEntry` 就创建好了。 8. **`compressDirectory()`**:不要忘记了,我们还处在第三步中的 `for` 循环当中,所以我们现在又回来了,接下来的步骤就交给你咯.. ## 解压简单的Zip ~~~ public static void demo01() throws Exception { File base = new File("C:" + File.separator + "test"); //先声明好要解压出的文件 File txtFile = new File(base, "1.txt"); //声明我们的 Zip 文件 ZipFile zipFile = new ZipFile(new File(base, "test.zip")); //返回指定名称的 entry,如果找不到则会返回 null ZipEntry entry = zipFile.getEntry("1.txt"); FileOutputStream out = new FileOutputStream(txtFile); //返回传入的 entry 的输入流 InputStream in = zipFile.getInputStream(entry); int temp = 0; while ((temp = in.read()) != -1) { out.write(temp); } out.close(); in.close(); } ~~~ ## 解压复杂的Zip 通过上面的代码,我们可以成功解压 Zip 文件,但是反映出的问题也比较多,例如:我们在解压的时候一般不知道 Zip 文件中每个 Entry 的名字,那么就无法通过 `getEntry` 方法获得 Entry;Zip 文件常见的都是会将一个文件夹进行压缩,所以现在我们就来试一下,如何将 Zip 文件中的复杂的嵌套文件夹进行解压。 >先插播一个 **for循环** 的一种使用形式(其实是我自己忘记了) > for(exp1; exp2; exp3) > exp1:第一次进入循环,会执行的操作,后面不在执行。非必须。 > exp2:判断条件,true继续循环,false退出循环。非必须。 > exp3:每次循环完成后置动作,每次都会执行。非必须。 > for(;;)无限循环 > **转载自**[百度知道的回答] ~~~ public static void main(String[] args) throws Exception { DecompressionDemo demo = new DecompressionDemo(); demo.decompress(new File("C:" + File.separator + "test" + File.separator + "demo.zip"), "C/test/"); } //decompress方法接收2个参数,zipFile代表Zip文件,DeCompressPath代表最后把文件解压到哪个路径下 public void decompress(File zipFile, String DeCompressPath) throws Exception { ZipFile zip = new ZipFile(zipFile, Charset.forName("GBK"));//解决可能发生的中文乱码问题 //获取Zip文件的文件名,如demo.zip,String name= demo; String name = zip.getName() .substring(zip.getName().lastIndexOf(File.separator) + 1, zip.getName().lastIndexOf(".")); //解压的时候,统一先创建一个与Zip文件同名的文件夹,再把Zip文件中的Entry放入 //在win系统下,此时File会把我们的路径转换为C:\test\demo File compressPathFile = new File(DeCompressPath + name); if (!compressPathFile.exists()) { compressPathFile.mkdirs(); } ZipEntry entry = null; InputStream in = null; FileOutputStream out = null; int temp = 0; // entries()方法返回Zip文件中所有的entry /*如 demo.zip 中包含一个demo文件夹,demo文件夹包含1.txt,2.txt,3(文件夹),3(文件夹)包含一个4.txt -demo(文件夹) -1.txt -2.txt -3(文件夹) -4.txt */ /*那么返回的所有entry的name就是: demo/ demo/1.txt demo/2.txt demo/3/ demo/3/4.txt 如果Zip文件中没有demo这个文件夹,那么返回的entry的name会是这样的形式 1.txt 2.txt 3/ 3/4.txt */ //File.separator 在win10系统下,会输出 "\" for (Enumeration<? extends ZipEntry> entries = zip.entries(); entries.hasMoreElements(); ) { entry = entries.nextElement(); String zipEntryName = entry.getName(); in = zip.getInputStream(entry); //File.toString()方法进行过重写,返回的是 File.getPath(),假设当前的entry是demo/3 String outPath = compressPathFile + File.separator + zipEntryName;//C:\test\demo\demo/3/ if (outPath.contains("/")) {//这里为了判断Zip文件中是否有文件夹嵌套 //同样地,定义File时,我们传入了 C:\test\demo\demo/3/ //这里会自动帮我们把file的路径转换为 C:\test\demo\demo\3 File file = new File(outPath.substring(0, outPath.lastIndexOf("/")));// C:\test\demo\demo\3 if (!file.exists()) { file.mkdirs();//创建 C:\test\demo\demo\3 这个文件夹 } if (new File(outPath).isDirectory()) {// 因为文件夹只需要创建,不需要IO传输数据,所以这里 continue continue; } } out = new FileOutputStream(outPath); while ((temp = in.read()) != -1) { out.write(temp); } in.close(); out.close(); temp = 0; } System.out.println("Done"); } ~~~ ## 总结 那么到这里,我们手上就有了 2 个工具类:一个可以压缩文件或文件夹,另一个可以解压 Zip 文件,大多代码均通过网上的文件收集拼凑。我作为初学者,通过推敲其中的代码思路逻辑,收获很多,希望可以帮助到想要了解 Java 关于解压 Zip 知识的人。 ## **参考自** [解压与压缩] [Zip解压与压缩] (个人认为该文章部分存在错误,阅读时需判断) [如何解压与压缩空的文件夹] [百度知道的回答]:https://zhidao.baidu.com/question/874055672717360852.html [如何解压与压缩空的文件夹]:http://blog.csdn.net/flexworks/article/details/6623750 [解压与压缩]:http://blog.csdn.net/ljheee/article/details/52736091 [ZipOutputStream]:https://docs.oracle.com/javase/7/docs/api/java/util/zip/ZipOutputStream.html [ZipInputStream]:https://docs.oracle.com/javase/7/docs/api/java/util/zip/ZipInputStream.html [ZipFile]:https://docs.oracle.com/javase/7/docs/api/java/util/zip/ZipFile.html [ZipEntry]:https://docs.oracle.com/javase/7/docs/api/java/util/zip/ZipEntry.html [Zip解压与压缩]:http://blog.csdn.net/dabing69221/article/details/17074763