专栏首页指点的专栏Java IO 操作基础2---操作 ZIP 压缩文件

Java IO 操作基础2---操作 ZIP 压缩文件

上一篇 Java 类别的文章中介绍了一下 Java 中普通文件的相关操作,包括:文件读取和写入、文件复制、文件移动等操作。

这一篇来看一下 Java API 对 ZIP 压缩格式的文件的相关操作。

一、 压缩文件/文件夹

先从压缩文件开始吧,先来看一下一个普通的压缩文件的内容:

这是一个简单的 ZIP 格式的压缩文件,打开之后可以看到里面有很多项,包括文件夹和文件,我们在压缩这些文件时往往会先将要压缩的文件选中,然后再将它们压缩成一个压缩文件。在 Java 的 ZIP 压缩文件 API 中,每一个文件/文件夹在压缩时都被看成是一个“入口”对象(ZipEntry 对象),压缩时,有几个文件/文件夹,就需要创建几个“入口”对象(ZipEntry 对象)。下面看一下压缩一个文件/文件夹的基本步骤:

假设现在对一个名为 a 的文件/文件夹进行压缩
1、判断 a 是否为一个文件/文件夹,如果 a 为一个文件,那么创建一个新的同名"入口"对象(ZipEntry 对象),并且运用 API 将文件 a 中的内容写入这个"入口"对象(ZipEntry 对象)中。结束压缩。

2、如果 a 是一个文件夹,那么我们仍需要创建一个新的同名"入口"对象(ZipEntry 对象),之后对 a 文件夹里面的每一个文件/文件夹进行递归压缩(因为我们并不知道 a 的子文件是否全是文件/全是文件夹)。

Ok,明白了压缩过程之后我们来看一下其相关的 API:

ZipOutputStream 类:我们知道,对普通文件操作时,如果需要将文件输出,则需要使用 OutputStream 的子类来进行写数据操作。同样的,对于 ZIP 格式压缩文件,我们需要用 ZipOutputStream 类来对其进行数据写入等操作。

其常用的方法有:

putNextEntry(ZipEntry e) // 在压缩文件中添加一个新的"入口"

close() // 结束数据写入并且关闭压缩文件流

write(byte[] b, int off, int len) // 将数组 b 中的数据写入数据到当前 ZIP 文件流中, off 为从数组 b 中开始读取的数据的偏移量(字节),len 为写入数据的长度(字节) 

finish () // 结束数据写入但是不关闭压缩文件流

setComment(String comment) // 设置压缩文件的注释,打开这个压缩文件时能看到
....

基本的 API 就这些了,下面来实践一下,压缩一个文件/文件夹,先上代码:

/**
 * compress file or dictionary what named inputName by zip form and save it to the load of outputName as a zip file
 * if param inputName is null or the file of it represent is not exists,
 * this method will throws new IllegalArgumentException;
 * 以 zip 格式压缩路径为 inputName 的文件/文件夹,并且将其压缩后的 zip 文件保存在路径为 outputName 的文件,
 * 如果 inputName 所代表的文件/文件夹不存在,将会抛出一个 IllegalArgumentException
 * @param inputName the file of you want to compress
 * @param outputName the output file path which you want to save the zip file
 */
public static void compressFile(@NotNull String inputName, @NotNull String outputName) throws IOException {
    if (inputName == null || outputName == null) {
        throw new IllegalArgumentException("input name and output name can't be null");
    }
    File inputFile = new File(inputName);
    if (!inputFile.exists()) {
        throw new IllegalArgumentException("The input file does not exists!");
    }
    // 创建一个新的 ZipOutputStream 对象
    ZipOutputStream output = new ZipOutputStream(new FileOutputStream(outputName));
    System.out.println("正在压缩中...");
    long startTime = System.currentTimeMillis();
    // 设置压缩文件注释
    output.setComment("This is my zip file");
    // 开始压缩文件
    zipFile(output, inputFile, "");
    long endTime = System.currentTimeMillis();
    System.out.println("压缩完成");
    System.out.println("压缩用时:" + String.valueOf(((double) endTime-startTime)/1000) + "秒");
    // 结束压缩文件
    output.close();
}

/**
 * compress current file as zip form and save the result file as the sub file of folder that basePath represent
 * 将当前文件(currentFile)压缩到 basePath 文件夹下,basePath 为相对压缩文件的相对路径
 * @param zOut the outputStream of zip file
 * @param currentFile current file that will be compressed
 * @param basePath the path of current file will be saved,relative to compress file
 */
private static void zipFile(@NotNull ZipOutputStream zOut, @NotNull File currentFile, @NotNull String basePath) {
    if (zOut == null || !currentFile.exists() || basePath == null) {
        return ;
    }
    // 处理文件夹的情况
    if (currentFile.isDirectory()) {
        File[] subFiles = currentFile.listFiles();
        try {
            // 建立一个新的压缩入口对象(ZipEntry)
            zOut.putNextEntry(new ZipEntry(basePath + "/"));
            // 如果当前文件对象为一个文件夹,那么需要递归压缩其子文件
            for (File f : subFiles) {
                zipFile(zOut, f, basePath + "/" + f.getName());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    // 处理文件的情况
    } else {
        try {
            // 建立一个新的压缩入口对象(ZipEntry)
            zOut.putNextEntry(new ZipEntry(basePath));
            BufferedInputStream in = new BufferedInputStream(new FileInputStream(currentFile));
            // 读取要压缩的文件的内容,并将其写入到压缩文件中
            byte[] bs = new byte[1024];
            while (in.read(bs) != -1) {
                zOut.write(bs);
            }
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

用了两个方法来实现文件/文件夹的压缩。compressFile 方法先检测输入的参数是否合法,并且创建了一个 ZipOutputStream 对象作为 zipFile 的参数来完成文件的压缩。 zipFile 方法主要是对要压缩的文件进行判断其是否是文件/文件夹。并对文件和文件夹分开处理。方法在上文已经详细讲过了。

这里需要注意的是,压缩文件的输出路径取决于创建 ZipOutputStream 时传入的 OutputStream 对象的输出目录。也就是说在创建”入口”对象 (ZipEntry 对象)时传入的路径参数为相对整个压缩文件的相对路径。ok,来试试:

我在工程目录下创建了一个 testFolder 文件夹,里面包含了一个文件本件(文本1.txt)和另一个子文件夹,这个子文件夹下有一个文本文件(魁拔之书.txt)。运行程序:

来看一下压缩后的文件:

可以看到,我们的文件成功压缩了。并且显示出了我们刚开始设置的注释内容。好了,下面看看解压 Zip 格式文件。

二、解压 ZIP 文件

压缩会了,解压就不难了。和压缩文件对应,解压 ZIP 文件可以对一个个 “入口” 对象来进行操作,同样的,有几个 “入口” 对象,就需要新建多少个文件/文件夹。然后读取 Zip 文件的每一个”入口“对象(ZipEntry) 对象,并将里面的数据读取到对应新建的文件/文件夹中。

我们通过 ZipFile 类来进行解压操作。下面来看一下相关 API :

来看一下 ZipFile 类常用方法:

1、entries() // 该方法返回一个 Enumeration 对象,里面包含了这个 ZIP 压缩文件的所有"入口"(ZipEntry 对象)。

2、getInputStream(ZipEntry entry) // 返回一个 InputStream 对象。用它来进行 ZIP 压缩文件的某个 "入口"(ZipEntry 对象)数据的读取。

3、getName() // 返回当前解压的文件名

4、getEntry() // 通过压缩时指定的文件名获取 "入口"对象(ZipEntry 对象)

5、size() // 返回 Zip 文件中 "入口"对象 (ZipEntry 对象) 的总数

下面同样来实践一下,对上文压缩的文件进行解压,先上代码:

/**
* decompress the zip file to specific path
 * 将 zip 文件解压缩到 outputName 所代表的文件夹中,确保 outputName 为一个已存在的文件夹
 * @param inputName the zip file path which you want to decompress
 * @param outputName the folder path what you want to save the result files,
 *                   please make sure the folder of outputName represent is exists
 */
public static void decompressFile(@NotNull String inputName, @NotNull String outputName) {
    if (inputName == null || outputName == null) {
        throw new IllegalArgumentException("input path and output path can't be null");
    }
    File outputDir = new File(outputName);
    // 如果输出目录不存在,那么新建一个文件夹
    if (!outputDir.exists()) {
        outputDir.mkdirs();
    }
    try {
        System.out.println("正在解压中...");
        long startTime = System.currentTimeMillis();
        // 创建 ZipFile 对象来解压 ZIP 文件
        ZipFile zipFile = new ZipFile(inputName);
        // 解压文件
        unzipFile(zipFile, outputName);
        long endTime = System.currentTimeMillis();
        zipFile.close();
        System.out.println("解压完成");
        System.out.println("解压用时:" + String.valueOf(((double) endTime-startTime)/1000) + "秒");
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
 * 解压缩 zip 文件(zf) 到 basePath 的文件夹下
 * @param zf the file of you want to decompress
 * @param basePath the folder what save the files that after decompressing
 */
private static void unzipFile(@NotNull ZipFile zf, @NotNull String basePath) {
    if (zf == null || basePath == null) {
        return;
    }
    // 获取 Zip 文件所有的 ZipEntry 对象,储存在一个 Enumeration 顺序容器对象中
    Enumeration<? extends ZipEntry> entries = zf.entries();

    // 一直处理压缩文件,直到处理完成所有的压缩文件入口对象(ZipEntry)
    for (ZipEntry entry; entries.hasMoreElements();) {
        entry = entries.nextElement();
        // 处理文件夹
        if (entry.isDirectory()) {
            // 当前解压缩文件夹的绝对路径
            File dirFile = new File(basePath + File.separator + entry.getName());
            if (!dirFile.exists()) {
                dirFile.mkdirs();
            }
        // 处理文件
        } else {
            // 当前解压缩文件的绝对路径
            File currentFile = new File(basePath + File.separator + entry.getName());
            currentFile.mkdirs();
            try {
                // 如果存在和解压文件同名的文件,删除这个文件并新建一个文件
                if (currentFile.exists()) {
                    currentFile.delete();
                }
                currentFile.createNewFile();
                // 将压缩文件中的内容读取出来,并写进解压缩文件中
                InputStream is = zf.getInputStream(entry);
                FileOutputStream fos = new FileOutputStream(currentFile);
                byte[] bs = new byte[1024];
                while (is.read(bs) != -1) {
                    fos.write(bs);
                }
                is.close();
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

基本上思路和压缩文件是类似的,同样的要注意,解压缩后的文件的输出目录取决于创建 ZipFile 对象时的参数的代表的路径。解压过程中创建的文件/文件夹都是基于这个路径的相对路径。

运行一下。来看看结果:

成功解压,来看一下解压后的文件目录:

Ok,和我们当时创建时相同。

最后给出完整的代码:

import com.sun.istack.internal.NotNull;

import java.io.*;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

/**
 * This is util class to compress file to a zip file, and decompress zip file to normal file
 * Author: 指点
 */

public class ZipUtils {

    /**
     * compress file or dictionary what named inputName by zip form and save it to the load of outputName as a zip file
     * if param inputName is null or the file of it represent is not exists,
     * this method will throws new IllegalArgumentException;
     * 以 zip 格式压缩路径为 inputName 的文件/文件夹,并且将其压缩后的 zip 文件保存在路径为 outputName 的文件,
     * 如果 inputName 所代表的文件/文件夹不存在,将会抛出一个 IllegalArgumentException
     * @param inputName the file of you want to compress
     * @param outputName the output file path which you want to save the zip file
     */
    public static void compressFile(@NotNull String inputName, @NotNull String outputName) throws IOException {
        if (inputName == null || outputName == null) {
            throw new IllegalArgumentException("input name and output name can't be null");
        }
        File inputFile = new File(inputName);
        if (!inputFile.exists()) {
            throw new IllegalArgumentException("The input file does not exists!");
        }
        // 创建一个新的 ZipOutputStream 对象
        ZipOutputStream output = new ZipOutputStream(new FileOutputStream(outputName));
        System.out.println("正在压缩中...");
        long startTime = System.currentTimeMillis();
        // 设置压缩文件注释
        output.setComment("This is my zip file");
        // 开始压缩文件
        zipFile(output, inputFile, "");
        long endTime = System.currentTimeMillis();
        System.out.println("压缩完成");
        System.out.println("压缩用时:" + String.valueOf(((double) endTime-startTime)/1000) + "秒");
        // 结束压缩文件
        output.close();
    }

    /**
     * decompress the zip file to specific path
     * 将 zip 文件解压缩到 outputName 所代表的文件夹中,确保 outputName 为一个已存在的文件夹
     * @param inputName the zip file path which you want to decompress
     * @param outputName the folder path what you want to save the result files,
     *                   please make sure the folder of outputName represent is exists
     */
    public static void decompressFile(@NotNull String inputName, @NotNull String outputName) {
        if (inputName == null || outputName == null) {
            throw new IllegalArgumentException("input path and output path can't be null");
        }
        File outputDir = new File(outputName);
        // 如果输出目录不存在,那么新建一个文件夹
        if (!outputDir.exists()) {
            outputDir.mkdirs();
        }
        try {
            System.out.println("正在解压中...");
            long startTime = System.currentTimeMillis();
            // 创建 ZipFile 对象来解压 ZIP 文件
            ZipFile zipFile = new ZipFile(inputName);
            // 解压文件
            unzipFile(zipFile, outputName);
            long endTime = System.currentTimeMillis();
            zipFile.close();
            System.out.println("解压完成");
            System.out.println("解压用时:" + String.valueOf(((double) endTime-startTime)/1000) + "秒");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * compress current file as zip form and save the result file as the sub file of folder that basePath represent
     * 将当前文件(currentFile)压缩到 basePath 文件夹下
     * @param zOut the outputStream of zip file
     * @param currentFile current file that will be compressed
     * @param basePath the path of current file will be saved
     */
    private static void zipFile(@NotNull ZipOutputStream zOut, @NotNull File currentFile, @NotNull String basePath) {
        if (zOut == null || !currentFile.exists() || basePath == null) {
            return ;
        }
        // 处理文件夹
        if (currentFile.isDirectory()) {
            File[] subFiles = currentFile.listFiles();
            try {
                // 建立一个新的压缩入口对象(ZipEntry)
                zOut.putNextEntry(new ZipEntry(basePath + "/"));
                // 如果当前文件对象为一个文件夹,那么需要递归压缩其子文件
                for (File f : subFiles) {
                    zipFile(zOut, f, basePath + "/" + f.getName());
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        // 处理文件
        } else {
            try {
                // 建立一个新的压缩入口对象(ZipEntry)
                zOut.putNextEntry(new ZipEntry(basePath));
                BufferedInputStream in = new BufferedInputStream(new FileInputStream(currentFile));
                // 读取要压缩的文件的内容,并将其写入到压缩文件中
                byte[] bs = new byte[1024];
                while (in.read(bs) != -1) {
                    zOut.write(bs);
                }
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 解压缩 zip 文件(zf) 到 basePath 的文件夹下
     * @param zf the file of you want to decompress
     * @param basePath the folder what save the files that after decompressing
     */
    private static void unzipFile(@NotNull ZipFile zf, @NotNull String basePath) {
        if (zf == null || basePath == null) {
            return;
        }
        // 获取 Zip 文件所有的 ZipEntry 对象,储存在一个 Enumeration 顺序容器对象中
        Enumeration<? extends ZipEntry> entries = zf.entries();

        // 一直处理压缩文件,直到处理完成所有的压缩文件入口对象(ZipEntry)
        for (ZipEntry entry; entries.hasMoreElements();) {
            entry = entries.nextElement();
            // 处理文件夹
            if (entry.isDirectory()) {
                // 当前解压缩文件夹的绝对路径
                File dirFile = new File(basePath + File.separator + entry.getName());
                if (!dirFile.exists()) {
                    dirFile.mkdirs();
                }
            // 处理文件
            } else {
                // 当前解压缩文件的绝对路径
                File currentFile = new File(basePath + File.separator + entry.getName());
                currentFile.mkdirs();
                try {
                    // 如果存在和解压文件同名的文件,删除这个文件并新建一个文件
                    if (currentFile.exists()) {
                        currentFile.delete();
                    }
                    currentFile.createNewFile();
                    // 将压缩文件中的内容读取出来,并写进解压缩文件中
                    InputStream is = zf.getInputStream(entry);
                    FileOutputStream fos = new FileOutputStream(currentFile);
                    byte[] bs = new byte[1024];
                    while (is.read(bs) != -1) {
                        fos.write(bs);
                    }
                    is.close();
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        try {
            compressFile("testFolder", "testFile.zip");
            decompressFile("testFile.zip", "F:/Java/Project/");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

总结来说:

1、压缩文件时一个文件/文件夹对应一个 "入口"对象(ZipEntry 对象),压缩文件时使用 ZipOutputStream 对象的相关方法完成。

2、解压文件时一个"入口"对象(ZipEntry 对象)对应一个文件/文件夹,解压文件使用 ZipFIle 对象的相关方法来完成

3、压缩文件 / 解压文件的输出目录取决于创建 ZipOutputStream 对象 / ZipFile 对象时参数所代表的路径,压缩/解压过程中创建的对象的路径都是相对于这个创建时参数所代表的路径的相对路径。

最后给出完整的工程文件(由 Idea 创建的工程):

完成的工程文件戳这里……

如果博客中有什么不正确的地方,还请多多指点,如果觉得本文对您有帮助,请不要吝啬您的赞。

谢谢观看。。。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java IO 操作基础1---普通文件的相关操作

    Java 中 IO 操作是 Java 的一个重要组成部分,这里总结一下 Java 中的 IO 的基础操作。

    指点
  • 51Nod--1005 大数加法

    题目:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1005

    指点
  • Android Service基础

    Service 作为Android的四大组件之一,如果没听过Service,怎么能说能说自己是一个Android开发者呢,实际上,Service 在Androi...

    指点
  • webpack简单搭建localhost访问静态资源

    前端开发过程中,静态页面直接双击HTML文件就能在浏览器打开,有时候我们很希望可以用localhost启动,在局域网内可以直接用手机或者是别的电脑访问。

    wade
  • Oracle 20c 新特性:强制大小写敏感密码文件 Force Password File Case Sensitive

    墨墨导读:从Oracle Database 20c开始,数据库强制实施大小写敏感的口令文件,以实现更高的安全性。区分大小写的密码文件提供更高的安全性。Oracl...

    数据和云
  • 编程小知识之 JavaScript 文件读取

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/...

    用户2615200
  • 《Pandas Cookbook》第02章 DataFrame基本操作1. 选取多个DataFrame列2. 对列名进行排序3. 在整个DataFrame上操作4. 串联DataFrame方法5. 在

    SeanCheney
  • 从零实现一个React(三):diff算法

    在上一篇文章,我们已经实现了React的组件功能,从功能的角度来说已经实现了React的核心功能了。

    前端迷
  • 企业安全建设的体系思考与落地实践

    企业安全建设是一个老生常谈的问题,由于每个人的工作经验和心得体会的不同,因此看法和实践通常也不一样。

    FB客服
  • Python入门基础教程-文件

    大多数情况下程序中的数据会来自于外部,包括数据库导出的规整化数据、爬虫获取的大量不规则数据、以及其他各企业内部数据。总之,要想对数据进行处理、你得先学会数据的读...

    知秋小一

扫码关注云+社区

领取腾讯云代金券