Java解压缩文件教程:从基础知识到实践应用,全面掌握文件压缩与解压技巧

Java 如何实现解压缩文件和文件夹

Java解压缩文件教程:从基础知识到实践应用,全面掌握文件压缩与解压技巧

2021 年 03 月 11 日 08 时 31 分 15 秒进行了更新,更新的作者是 Mr_ηobody。

感兴趣的朋友可以对这些内容进行了解。

目录三 解压文件四 总结

一 前言

在项目开发过程中,时常会碰到解压缩文件的情况。例如,当用户下载多个文件时,服务端具备将这些多个文件压缩成一个文件的能力,像 xx.zip 或者 xx.rar 这样的形式。而在用户上传资料的时候,是允许上传压缩文件的,之后服务端会对其进行解压并读取每一个文件。

因为具有通用性,所以介绍几种能够解压缩文件的方式,并且将这些方式包装成工具类,以便在平时的开发中使用。

二 压缩文件

压缩文件,意思就是把一个或者多个文件压缩成一个文件。压缩存在两种形式,一种是把所有文件都压缩到同一个目录下,在这种方式下需要注意文件重名被覆盖的问题。另一种是依据原有文件树的结构来进行压缩,也就是压缩之后的文件树结构依然保持不变。

在进行压缩文件操作时,会用到一个类,这个类是 ZipOutputStream。

2.1 压缩多个文件

此方法会把所有文件压缩至同一个目录里。该方法会接收多个文件列表以及一个最终要压缩到的文件路径名。

  /**
   * 压缩多个文件,压缩后的所有文件在同一目录下
   * 
   * @param zipFileName 压缩后的文件名
   * @param files 需要压缩的文件列表
   * @throws IOException IO异常
   */
公共静态 void 进行多个文件的压缩(字符串压缩文件名,文件...多个文件)抛出 IOException {
创建一个 ZipOutputStream 实例,该实例初始值为 null 。
    try {
      // 输出流
创建了一个 ZipOutputStream 对象,该对象使用一个 FileOutputStream 对象来写入文件,而这个 FileOutputStream 对象的参数是指定的 zip 文件名 zipFileName 。
      // 遍历每一个文件,进行输出
      for (File file : files) {
zipOutputStream 开始写入一个新的条目,该条目名为 file 的名称;zipOutputStream 为新的条目做好了准备,新条目是通过创建一个新的 ZipEntry,其名称为 file 的名称来实现的;zipOutputStream 进入到下一个条目,这个条目就是以 file 的名称命名的 ZipEntry 。
创建了一个 FileInputStream 对象,该对象用于读取指定的文件。具体来说,通过传入一个文件对象 file,初始化了这个 FileInputStream 实例,即 fileInputStream 现在与指定的文件建立了连接,可以从该文件中读取数据。
        int readLen;
创建了一个长度为 1024 的字节数组 buffer 。
当 (readLen = fileInputStream.read(buffer)) 不等于 -1 时,{
zipOutputStream 把 buffer 从索引 0 开始写,写入的长度为 readLen 。
        }
        // 关闭流
        fileInputStream.close();
        zipOutputStream.closeEntry();
      }
    } finally {
如果 zipOutputStream 不为空。
        try {
          zipOutputStream.close();
        } catch (IOException ex) {
          ex.printStackTrace();
        }
      }
    }
  }

测试,把 D 盘下的 infp.txt 进行压缩,压缩到 D 盘下,同时把 D 盘下的 infp1.txt 也进行压缩,压缩到 D 盘下,压缩后的文件名为 my.zip。

公共静态 void 主函数(字符串数组参数)抛出异常 {
调用 zipMultipleFiles 函数,传入参数 "D:/my.zip" 以及分别位于 "D:/infp.txt" 和 "D:/infp1.txt" 的两个文件对象。
  }

2.2 压缩文件或文件树

此方法能把文件夹下的所有文件依照原有的树形结构压缩到文件里,并且还支持压缩单个文件。其原理较为简单,就是对文件树中的每一个文件进行递归遍历,然后进行压缩。需要注意的是,每一个文件的写入路径是相对于压缩文件位置的相对路径。

package com.nobody.zip;
import java.io.*;
使用了 java.util.zip 包中的 ZipEntry 类。
导入 Java 中的类 java.util.zip.ZipInputStream 。
引入 Java 中的类 java.util.zip.ZipOutputStream ;
public class ZipUtils {
  /**
   * 压缩文件或文件夹(包括所有子目录文件)
   *
   * @param sourceFile 源文件
   * @param format 格式(zip或rar)
   * @throws IOException 异常信息
   */
公共静态 void 压缩文件树(File 源文件,String 格式)抛出 IOException {
    ZipOutputStream zipOutputStream = null;
    try {
      String zipFileName;
如果 sourceFile 是一个目录。  // 目录意味着包含多个文件或其他目录。  // 这种情况下需要对目录中的内容进行相应处理。
zipFileName 等于 sourceFile 的父目录加上文件分隔符再加上 sourceFile 的名称然后加上点号。
            + format;
      } else { // 单个文件
zipFileName 等于 sourceFile 的父目录。
获取 sourceFile 的名称,然后截取该名称从开头到最后一个点之前的部分
            + "." + format;
      }
      // 压缩输出流
      zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFileName));
使用 zip 函数将 sourceFile 压缩到 zipOutputStream 中,并传入空字符串作为参数。
    } finally {
      if (null != zipOutputStream) {
        // 关闭流
        try {
          zipOutputStream.close();
        } catch (IOException ex) {
          ex.printStackTrace();
        }
      }
    }
  }
  /**
   * 递归压缩文件
   * 
   * @param file 当前文件
定义了一个名为 zipOutputStream 的参数,它是用于输出压缩数据的流。
   * @param relativePath 相对路径
   * @throws IOException IO异常
   */
静态私有方法用于将文件进行压缩操作。该方法接收一个文件对象、一个 ZipOutputStream 对象以及一个相对路径字符串作为参数。通过这些参数,方法可以实现对指定文件的压缩处理,并将其写入到 ZipOutputStream 中,同时根据相对路径进行相应的存储和组织。
      throws IOException {
创建了一个名为 fileInputStream 的 FileInputStream 对象,并且将其初始化为 null 。
    try {
如果文件是一个目录,那么意味着当前处于文件夹状态。
        // 当前文件夹下的所有文件
获取到的文件数组 list 是通过 file 调用 listFiles()方法得到的。
        if (null != list) {
          // 计算当前的相对路径
如果 relativePath 的长度为 0,那么 relativePath 就等于空字符串;否则,relativePath 等于自身加上一个斜杠“/”,然后再加上 file 的名称。
          // 递归压缩每个文件
          for (File f : list) {
调用 zip 函数,将函数 f、zipOutputStream 和相对路径 relativePath 作为参数传入。
          }
        }
      } else { // 压缩文件
        // 计算文件的相对路径
如果 relativePath 的长度为 0 ,则 relativePath 加上空字符串;否则,relativePath 加上 "/" 再加上 file 的名称。
        // 写入单个文件
zipOutputStream 开始写入下一个条目,该条目为相对路径 relativePath 对应的 ZipEntry
创建了一个 FileInputStream 对象,该对象用于读取指定的文件,这个指定的文件由 file 表示,然后将创建的 FileInputStream 对象赋值给了 fileInputStream 。
        int readLen;
        byte[] buffer = new byte[1024];
        while ((readLen = fileInputStream.read(buffer)) != -1) {
          zipOutputStream.write(buffer, 0, readLen);
        }
        zipOutputStream.closeEntry();
      }
    } finally {
      // 关闭流
如果 fileInputStream 不为空。
        try {
          fileInputStream.close();
        } catch (IOException ex) {
          ex.printStackTrace();
        }
      }
    }
  }
  public static void main(String[] args) throws Exception {
    String path = "D:/test";
    String format = "zip";
使用新的 File 对象和指定的格式来创建 zip 文件树,参数为 path 和 format 。
  }
}

将 test 目录下的所有文件压缩到同一目录下的一个文件,这个文件是 test.zip 。

2.3 借助文件访问器压缩

还有一种更简便的途径,我们无需亲自去编写递归遍历的代码。通过借助 Java 的原生类 SimpleFileVisitor,它具备几个用于访问文件的方法,其中有一个方法叫做 visitFile,每当遇到文件树中的每一个文件(不包括文件夹)时,就会调用这个方法。我们写一个类去继承 SimpleFileVisitor ,接着重写 visitFile 方法,这样就能把每一个文件写入到压缩文件当中。

当然,它里面除了有 visitFile 方法之外,还有 preVisitDirectory 方法、postVisitDirectory 方法以及 visitFileFailed 方法。通过这些方法的名字,大家也能够猜出它们的意思了。

package com.nobody.zip;
导入 Java 的文件输出流类。具体来说,是导入了 java.io 包中的 FileOutputStream 类,以便能够进行文件输出操作。
import java.io.IOException;
import java.nio.file.*;
导入 Java 的 nio.file.attribute 包中的 BasicFileAttributes 类。
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
 * @Description
 * @Author Mr.nobody
 * @Date 2021/3/8
 * @Version 1.0.0
 */
公共类 ZipFileTree 是 SimpleFileVisitor 的子类。 {
  // zip输出流
创建了一个私有类型的 ZipOutputStream 对象,名为 zipOutputStream 。
  // 源目录
  private Path sourcePath;
  public ZipFileTree() {}
  /**
   * 压缩目录以及所有子目录文件
   *
   * @param sourceDir 源目录
   */
公共的 void 方法 zipFile,其参数为字符串 sourceDir,该方法会抛出 IOException 异常。
    try {
      // 压缩后的文件和源目录在同一目录下
字符串 zipFileName 等于 sourceDir 加上 ".zip"。
创建了一个 ZipOutputStream 对象,该对象是通过将一个 FileOutputStream 对象(用于写入文件)与指定的 zip 文件名(zipFileName)相关联而创建的,即 this.zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFileName))。
this.sourcePath 是通过 Paths.get(sourceDir) 得到的。
      // 开始遍历文件树
使用 Files 类的 walkFileTree 方法对 sourcePath 进行遍历操作,并且传入了 this 作为参数。
    } finally {
      // 关闭流
      if (null != zipOutputStream) {
        zipOutputStream.close();
      }
    }
  }
  // 遍历到的每一个文件都会执行此方法
  @Override
公共的 FileVisitResult 访问文件(路径为 file,基本文件属性为 attributes)并抛出 IOException。
    // 取相对路径
Path 的 targetFile 是 sourcePath 对 file 进行的相对化处理。
    // 写入单个文件
zipOutputStream 开始写入一个新的条目,这个条目对应的文件是 targetFile 转换后的字符串形式。
通过 Files 类的 readAllBytes 方法读取文件,得到的结果是一个字节数组 bytes 。
zipOutputStream 把字节数组 bytes 中的数据写入,起始位置是 0,写入的长度是 bytes 的长度。
    zipOutputStream.closeEntry();
    // 继续遍历
返回 FileVisitResult.CONTINUE 。
  }
  // 遍历每一个目录时都会调用的方法
  @Override
公共的 FileVisitResult 在访问目录之前(Path 类型的 dir,BasicFileAttributes 类型的 attrs)
      throws IOException {
调用父类的 preVisitDirectory 方法,传入目录 dir 和属性 attrs 并返回结果。
  }
  // 遍历完一个目录下的所有文件后,再调用这个目录的方法
  @Override
公共的 FileVisitResult 在访问目录(Path dir)后进行后续操作,期间可能会抛出 IOException 异常(IOException exc),然后返回处理结果。
调用父类的 postVisitDirectory 方法,传入目录 dir 和异常 exc 并返回结果。
  }
  // 遍历文件失败后调用的方法
  @Override
公共的文件访问结果在访问文件失败时(路径为文件,抛出的异常为输入输出异常)会抛出输入输出异常。
调用父类的方法 visitFileFailed,传入文件对象 file 和异常对象 exc,以实现访问文件失败的相关操作。
  }
公共静态的 void 方法 main,其参数为 String 类型的数组 args,并且会抛出 IOException 异常。
    // 需要压缩源目录
    String sourceDir = "D:/test";
    // 压缩
new ZipFileTree 对 sourceDir 进行了 zipFile 操作。
  }
}

三 解压文件

解压压缩包时,借助 ZipInputStream 类,能够读取到压缩包中的每一个文件。接着,依据读取到的文件属性,将其写入到相应路径下。倘若解压的压缩包是文件树的结构,那么每读取到一个文件后,如果该文件处于多层路径下,就需要先创建父目录,然后再写入文件流。

package com.nobody.zip;
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
 * @Description 解压缩文件工具类
 * @Author Mr.nobody
 * @Date 2021/3/8
 * @Version 1.0.0
 */
public class ZipUtils {
  /**
   * 解压
   * 
   * @param zipFilePath 带解压文件
   * @param desDirectory 解压到的目录
   * @throws Exception
   */
公共静态 void 进行解压(字符串 zipFilePath,字符串 desDirectory)抛出异常 {
创建一个名为 desDir 的 File 对象,该对象指向 desDirectory 所指定的目录。
    if (!desDir.exists()) {
desDir.mkdir()的结果赋值给 mkdirSuccess,判断 mkdirSuccess 是否为 true,若为 true 则表示创建目录成功,若为 false 则表示创建目录失败。
      if (!mkdirSuccess) {
创建解压目标文件夹时出现了失败的情况,于是抛出了一个新的异常。
      }
    }
    // 读入流
ZipInputStream 是一个用于读取 ZIP 文件的输入流。它通过将一个文件输入流包装在其中来实现对 ZIP 文件的读取操作。这里创建了一个 ZipInputStream 对象,其构造函数接受一个 FileInputStream 对象作为参数,该 FileInputStream 对象用于读取指定的 ZIP 文件路径(zipFilePath)。
    // 遍历每一个文件
ZipInputStream 中的下一个条目是 ZipEntry,这个 ZipEntry 为 zipEntry 。
    while (zipEntry != null) {
如果 zipEntry 是一个文件夹。
字符串 unzipFilePath 等于 desDirectory 加上 File.separator 再加上 zipEntry 的 getName() ;
        // 直接创建
创建一个新的文件对象,该对象的路径为 unzipFilePath,然后创建这个文件对象所对应的目录。
      } else { // 文件
        String unzipFilePath = desDirectory + File.separator + zipEntry.getName();
创建了一个名为 file 的 File 对象,该对象指向 unzipFilePath 所指定的路径。
        // 创建父目录
        mkdir(file.getParentFile());
        // 写出文件流
BufferedOutputStream 是一个输出流的缓冲类。它可以提高输出流的效率,减少磁盘 I/O 操作。通过使用缓冲,数据可以先存储在缓冲区中,然后一次性写入磁盘,而不是每次写入一个字节或字符。这样可以减少磁盘 I/O 操作的次数,提高程序的性能。在使用 BufferedOutputStream 时,需要将其与一个输出流关联起来,例如 FileOutputStream 或 OutputStreamWriter。然后,可以使用 write 方法将数据写入缓冲区,或者使用 flush 方法将缓冲区中的数据写入磁盘。
创建一个新的 FileOutputStream 对象,用于将数据写入到指定的解压缩文件路径中;然后,使用这个 FileOutputStream 对象创建一个新的 BufferedOutputStream 对象,以提供缓冲功能,从而提高文件写入的效率。
创建了一个长度为 1024 的字节数组,其名称为 bytes,字节数组的类型为 byte[]。
        int readLen;
当 (readLen = zipInputStream.read(bytes)) 不等于 -1 时:
bufferedOutputStream 向其中写入了字节数组 bytes,起始位置为 0,写入的长度为 readLen。
        }
        bufferedOutputStream.close();
      }
      zipInputStream.closeEntry();
zipInputStream获取到下一个条目并赋值给 zipEntry 。
    }
    zipInputStream.close();
  }
  // 如果父目录不存在则创建
如果文件对象的父目录为空,则创建当前目录。
如果文件为空或者文件存在,那么就会满足条件。
      return;
    }
    mkdir(file.getParentFile());
    file.mkdir();
  }
  public static void main(String[] args) throws Exception {
字符串 zipFilePath 的值为 "D:/test.zip"。
    String desDirectory = "D:/a";
将文件进行解压操作,解压的源文件路径为 zipFilePath,解压后的目标目录为 desDirectory。
  }
}

四 总结

解压缩文件时,主要进行的是对流的读取操作。要注意进行异常处理,同时还要关闭流。

在 web 应用里,借助接口能够达成文件的上传与下载。相应地,我们只需将压缩后的文件写入到 response.getOutputStream()这个输出流当中就可以了。

解压缩文件时,注意空文件夹的处理。

此演示项目被上传到了 Github 。如果有需要的话,你可以自行去下载它。也欢迎你对它进行 Star 操作。

你可能想看:
« 上一篇
下一篇 »

相关推荐

java中什么是冒泡,冒泡算法

2026年09月06日

27阅读

java中冒号,java双冒号

2026年09月06日

24阅读

java中的->,java中的是什么意思

2026年09月06日

24阅读

java中的super用法,Java中super的说明

2026年09月06日

27阅读

java中反射的应用,java中反射的应用场景

2026年09月06日

23阅读

java中开关的定义,Java关键字有哪些

2026年09月06日

25阅读