[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
压缩一,两个文件显然是无法满足大多数情况的,我们更需要的是一种“万能”的压缩工具类,即:我就告诉你我想要压缩的文件或者文件夹在哪,叫什么名字,你直接帮我压缩就好。这里为大家演示一种采用**递归**形式的,可以实现这一效果。
首先看一下我们要压缩的文件夹:

~~~
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
