一 前言

项目开发中,总会遇到解压缩文件的时候。比如,用户下载多个文件时,服务端可以将多个文件压缩成一个文件(例如xx.zip或xx.rar)。用户上传资料时,允许上传压缩文件,服务端进行解压读取每一个文件。

基于通用性,以下介绍几种解压缩文件的方式,包装成工具类,供平时开发使用。

一 压缩文件

压缩文件,顾名思义,即把一个或多个文件压缩成一个文件。压缩也有2种形式,一种是将所有文件压缩到同一目录下,此种方式要注意文件重名覆盖的问题。另一种是按原有文件树结构进行压缩,即压缩后的文件树结构保持不变。

压缩文件操作,会使用到一个类,即ZipOutputStream。

1.1 压缩多个文件

此方法将所有文件压缩到同一个目录下。方法传入多个文件列表,和一个最终压缩到的文件路径名。

  1. /**
  2. * 压缩多个文件,压缩后的所有文件在同一目录下
  3. *
  4. * @param zipFileName 压缩后的文件名
  5. * @param files 需要压缩的文件列表
  6. * @throws IOException IO异常
  7. */
  8. public static void zipMultipleFiles(String zipFileName, File... files) throws IOException {
  9. ZipOutputStream zipOutputStream = null;
  10. try {
  11. // 输出流
  12. zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFileName));
  13. // 遍历每一个文件,进行输出
  14. for (File file : files) {
  15. zipOutputStream.putNextEntry(new ZipEntry(file.getName()));
  16. FileInputStream fileInputStream = new FileInputStream(file);
  17. int readLen;
  18. byte[] buffer = new byte[1024];
  19. while ((readLen = fileInputStream.read(buffer)) != -1) {
  20. zipOutputStream.write(buffer, 0, readLen);
  21. }
  22. // 关闭流
  23. fileInputStream.close();
  24. zipOutputStream.closeEntry();
  25. }
  26. } finally {
  27. if (null != zipOutputStream) {
  28. try {
  29. zipOutputStream.close();
  30. } catch (IOException ex) {
  31. ex.printStackTrace();
  32. }
  33. }
  34. }
  35. }

测试,将D盘下的infp.txt和infp1.txt文件压缩到D盘下,压缩文件名为my.zip。

  1. public static void main(String[] args) throws Exception {
  2. zipMultipleFiles("D:/my.zip", new File("D:/infp.txt"), new File("D:/infp1.txt"));
  3. }

1.2 压缩文件或文件树

此方法将文件夹下的所有文件按原有的树形结构压缩到文件中,也支持压缩单个文件。原理也简单,无非就是递归遍历文件树中的每一个文件,进行压缩。有个注意的点每一个文件的写入路径是基于压缩文件位置的相对路径。

  1. package com.nobody.zip;
  2. import java.io.*;
  3. import java.util.zip.ZipEntry;
  4. import java.util.zip.ZipInputStream;
  5. import java.util.zip.ZipOutputStream;
  6. public class ZipUtils {
  7. /**
  8. * 压缩文件或文件夹(包括所有子目录文件)
  9. *
  10. * @param sourceFile 源文件
  11. * @param format 格式(zip或rar)
  12. * @throws IOException 异常信息
  13. */
  14. public static void zipFileTree(File sourceFile, String format) throws IOException {
  15. ZipOutputStream zipOutputStream = null;
  16. try {
  17. String zipFileName;
  18. if (sourceFile.isDirectory()) { // 目录
  19. zipFileName = sourceFile.getParent() + File.separator + sourceFile.getName() + "."
  20. + format;
  21. } else { // 单个文件
  22. zipFileName = sourceFile.getParent()
  23. + sourceFile.getName().substring(0, sourceFile.getName().lastIndexOf("."))
  24. + "." + format;
  25. }
  26. // 压缩输出流
  27. zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFileName));
  28. zip(sourceFile, zipOutputStream, "");
  29. } finally {
  30. if (null != zipOutputStream) {
  31. // 关闭流
  32. try {
  33. zipOutputStream.close();
  34. } catch (IOException ex) {
  35. ex.printStackTrace();
  36. }
  37. }
  38. }
  39. }
  40. /**
  41. * 递归压缩文件
  42. *
  43. * @param file 当前文件
  44. * @param zipOutputStream 压缩输出流
  45. * @param relativePath 相对路径
  46. * @throws IOException IO异常
  47. */
  48. private static void zip(File file, ZipOutputStream zipOutputStream, String relativePath)
  49. throws IOException {
  50. FileInputStream fileInputStream = null;
  51. try {
  52. if (file.isDirectory()) { // 当前为文件夹
  53. // 当前文件夹下的所有文件
  54. File[] list = file.listFiles();
  55. if (null != list) {
  56. // 计算当前的相对路径
  57. relativePath += (relativePath.length() == 0 ? "" : "/") + file.getName();
  58. // 递归压缩每个文件
  59. for (File f : list) {
  60. zip(f, zipOutputStream, relativePath);
  61. }
  62. }
  63. } else { // 压缩文件
  64. // 计算文件的相对路径
  65. relativePath += (relativePath.length() == 0 ? "" : "/") + file.getName();
  66. // 写入单个文件
  67. zipOutputStream.putNextEntry(new ZipEntry(relativePath));
  68. fileInputStream = new FileInputStream(file);
  69. int readLen;
  70. byte[] buffer = new byte[1024];
  71. while ((readLen = fileInputStream.read(buffer)) != -1) {
  72. zipOutputStream.write(buffer, 0, readLen);
  73. }
  74. zipOutputStream.closeEntry();
  75. }
  76. } finally {
  77. // 关闭流
  78. if (fileInputStream != null) {
  79. try {
  80. fileInputStream.close();
  81. } catch (IOException ex) {
  82. ex.printStackTrace();
  83. }
  84. }
  85. }
  86. }
  87. public static void main(String[] args) throws Exception {
  88. String path = "D:/test";
  89. String format = "zip";
  90. zipFileTree(new File(path), format);
  91. }
  92. }

上例将test目录下的所有文件压缩到同一目录下的test.zip文件中。

1.3 借助文件访问器压缩

还有一种更简单的方式,我们不自己写递归遍历。借助Java原生类,SimpleFileVisitor,它提供了几个访问文件的方法,其中有个方法visitFile,对于文件树中的每一个文件(文件夹除外),都会调用这个方法。我们只要写一个类继承SimpleFileVisitor,然后重写visitFile方法,实现将每一个文件写入到压缩文件中即可。

当然,除了visitFile方法,它里面还有preVisitDirectory,postVisitDirectory,visitFileFailed等方法,通过方法名大家也猜出什么意思了。

  1. package com.nobody.zip;
  2. import java.io.FileOutputStream;
  3. import java.io.IOException;
  4. import java.nio.file.*;
  5. import java.nio.file.attribute.BasicFileAttributes;
  6. import java.util.zip.ZipEntry;
  7. import java.util.zip.ZipOutputStream;
  8. /**
  9. * @Description
  10. * @Author Mr.nobody
  11. * @Date 2021/3/8
  12. * @Version 1.0.0
  13. */
  14. public class ZipFileTree extends SimpleFileVisitor<Path> {
  15. // zip输出流
  16. private ZipOutputStream zipOutputStream;
  17. // 源目录
  18. private Path sourcePath;
  19. public ZipFileTree() {}
  20. /**
  21. * 压缩目录以及所有子目录文件
  22. *
  23. * @param sourceDir 源目录
  24. */
  25. public void zipFile(String sourceDir) throws IOException {
  26. try {
  27. // 压缩后的文件和源目录在同一目录下
  28. String zipFileName = sourceDir + ".zip";
  29. this.zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFileName));
  30. this.sourcePath = Paths.get(sourceDir);
  31. // 开始遍历文件树
  32. Files.walkFileTree(sourcePath, this);
  33. } finally {
  34. // 关闭流
  35. if (null != zipOutputStream) {
  36. zipOutputStream.close();
  37. }
  38. }
  39. }
  40. // 遍历到的每一个文件都会执行此方法
  41. @Override
  42. public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) throws IOException {
  43. // 取相对路径
  44. Path targetFile = sourcePath.relativize(file);
  45. // 写入单个文件
  46. zipOutputStream.putNextEntry(new ZipEntry(targetFile.toString()));
  47. byte[] bytes = Files.readAllBytes(file);
  48. zipOutputStream.write(bytes, 0, bytes.length);
  49. zipOutputStream.closeEntry();
  50. // 继续遍历
  51. return FileVisitResult.CONTINUE;
  52. }
  53. // 遍历每一个目录时都会调用的方法
  54. @Override
  55. public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
  56. throws IOException {
  57. return super.preVisitDirectory(dir, attrs);
  58. }
  59. // 遍历完一个目录下的所有文件后,再调用这个目录的方法
  60. @Override
  61. public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
  62. return super.postVisitDirectory(dir, exc);
  63. }
  64. // 遍历文件失败后调用的方法
  65. @Override
  66. public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
  67. return super.visitFileFailed(file, exc);
  68. }
  69. public static void main(String[] args) throws IOException {
  70. // 需要压缩源目录
  71. String sourceDir = "D:/test";
  72. // 压缩
  73. new ZipFileTree().zipFile(sourceDir);
  74. }
  75. }

三 解压文件

解压压缩包,借助ZipInputStream类,可以读取到压缩包中的每一个文件,然后根据读取到的文件属性,写入到相应路径下即可。对于解压压缩包中是文件树的结构,每读取到一个文件后,如果是多层路径下的文件,需要先创建父目录,再写入文件流。

  1. package com.nobody.zip;
  2. import java.io.*;
  3. import java.util.zip.ZipEntry;
  4. import java.util.zip.ZipInputStream;
  5. import java.util.zip.ZipOutputStream;
  6. /**
  7. * @Description 解压缩文件工具类
  8. * @Author Mr.nobody
  9. * @Date 2021/3/8
  10. * @Version 1.0.0
  11. */
  12. public class ZipUtils {
  13. /**
  14. * 解压
  15. *
  16. * @param zipFilePath 带解压文件
  17. * @param desDirectory 解压到的目录
  18. * @throws Exception
  19. */
  20. public static void unzip(String zipFilePath, String desDirectory) throws Exception {
  21. File desDir = new File(desDirectory);
  22. if (!desDir.exists()) {
  23. boolean mkdirSuccess = desDir.mkdir();
  24. if (!mkdirSuccess) {
  25. throw new Exception("创建解压目标文件夹失败");
  26. }
  27. }
  28. // 读入流
  29. ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipFilePath));
  30. // 遍历每一个文件
  31. ZipEntry zipEntry = zipInputStream.getNextEntry();
  32. while (zipEntry != null) {
  33. if (zipEntry.isDirectory()) { // 文件夹
  34. String unzipFilePath = desDirectory + File.separator + zipEntry.getName();
  35. // 直接创建
  36. mkdir(new File(unzipFilePath));
  37. } else { // 文件
  38. String unzipFilePath = desDirectory + File.separator + zipEntry.getName();
  39. File file = new File(unzipFilePath);
  40. // 创建父目录
  41. mkdir(file.getParentFile());
  42. // 写出文件流
  43. BufferedOutputStream bufferedOutputStream =
  44. new BufferedOutputStream(new FileOutputStream(unzipFilePath));
  45. byte[] bytes = new byte[1024];
  46. int readLen;
  47. while ((readLen = zipInputStream.read(bytes)) != -1) {
  48. bufferedOutputStream.write(bytes, 0, readLen);
  49. }
  50. bufferedOutputStream.close();
  51. }
  52. zipInputStream.closeEntry();
  53. zipEntry = zipInputStream.getNextEntry();
  54. }
  55. zipInputStream.close();
  56. }
  57. // 如果父目录不存在则创建
  58. private static void mkdir(File file) {
  59. if (null == file || file.exists()) {
  60. return;
  61. }
  62. mkdir(file.getParentFile());
  63. file.mkdir();
  64. }
  65. public static void main(String[] args) throws Exception {
  66. String zipFilePath = "D:/test.zip";
  67. String desDirectory = "D:/a";
  68. unzip(zipFilePath, desDirectory);
  69. }
  70. }

四 总结

  • 在解压缩文件过程中,主要是对流的读取操作,注意进行异常处理,以及关闭流。
  • web应用中,通过接口可以实现文件上传下载,对应的我们只要把压缩后的文件,写入到response.getOutputStream()输出流即可。
  • 解压缩文件时,注意空文件夹的处理。

此演示项目已上传到Github,如有需要可自行下载,欢迎 Star 。 https://github.com/LucioChn/common-utils

Java实现解压缩文件和文件夹的更多相关文章

  1. java压缩/解压缩zip格式文件

    因为项目要用到压缩.解压缩zip格式压缩包,只好自己封装一个,对于网上流行的中文乱码的问题,本文的解决方法是用apache的包代替jdk里的.基本上还是比较好用的. 废话少说,直接上代码. }     ...

  2. Java实现FTP文件与文件夹的上传和下载

    Java实现FTP文件与文件夹的上传和下载 FTP 是File Transfer Protocol(文件传输协议)的英文简称,而中文简称为"文传协议".用于Internet上的控制 ...

  3. Java 代码完成删除文件、文件夹操作

    import java.io.File;/** * 删除文件和目录 * */public class DeleteFileUtil {    /**     * 删除文件,可以是文件或文件夹     ...

  4. Java 基础【13】 文件(文件夹) 创建和删除

    使用 java.io.file 创建文件(文件夹),算是 java 最基础的知识,但实战项目中还是需要知晓细节. 比如 File 类中的 mkdir() 和 mkdirs() 的区别. JDK API ...

  5. java文件和文件夹复制、删除、移动操作

    java文件和文件夹复制.删除.移动操作 import java.io.File; import java.io.FileInputStream; import java.io.FileOutputS ...

  6. Java递归输出指定路径下所有文件及文件夹

    package a.ab; import java.io.File; import java.io.IOException; public class AE { public static void ...

  7. Java中创建操作文件和文件夹的工具类

    Java中创建操作文件和文件夹的工具类 FileUtils.java import java.io.BufferedInputStream; import java.io.BufferedOutput ...

  8. java.util.zip压缩打包文件总结一:压缩文件及文件下面的文件夹

    一.简述 zip用于压缩和解压文件.使用到的类有:ZipEntry  ZipOutputStream 二.具体实现代码 package com.joyplus.test; import java.io ...

  9. Java创建、重命名、删除文件和文件夹(转)

    Java的文件操作太基础,缺乏很多实用工具,比如对目录的操作,支持就非常的差了.如果你经常用Java操作文件或文件夹,你会觉得反复编写这些代码是令人沮丧的问题,而且要大量用到递归. 下面是的一个解决方 ...

随机推荐

  1. 【DP】区间DP入门

    在开始之前我要感谢y总,是他精彩的讲解才让我对区间DP有较深的认识. 简介 一般是线性结构上的对区间进行求解最值,计数的动态规划.大致思路是枚举断点,然后对断点两边求取最优解,然后进行合并从而得解. ...

  2. js 可选链 & 空值合并 In Action

    js 可选链 & 空值合并 In Action const obj = { props: { name: 'eric', }, // prop, 不存在的属性 ️ }; console.log ...

  3. vue template

    vue template <template> <div class="custom-class"> ... </div> </templ ...

  4. Python 2 to Python 3 convert

    Python 2 to Python 3 convert 2to3, 自动将 Python 2 代码转为 Python 3 代码 https://docs.python.org/zh-cn/2/lib ...

  5. react fiber

    react fiber https://github.com/acdlite/react-fiber-architecture https://github.com/facebook/react/is ...

  6. js showOpenFilePicker showSaveFilePicker showDirectoryPicker API

    选择文件,获取文件句柄 btn.addEventListener("click", async (e) => { try { const hFiles = await win ...

  7. 若依管理系统RuoYi-Vue(二):权限系统设计详解

    若依Vue系统中的权限管理部分的功能都集中在了系统管理菜单模块中,如下图所示.其中权限部分主要涉及到了用户管理.角色管理.菜单管理.部门管理这四个部分. 一.若依Vue系统中的权限分类 根据观察,若依 ...

  8. django学习-20.python3中的特殊方法【__str__】的作用

    目录结构 1.前言 2.[__str__]特殊方法的具体使用 2.1.当使用print打印一个类被实例化后生成的对象的时候,若类里有定义了[__str__]特殊方法,是打印出这样的数据:[__str_ ...

  9. 阿里云linux安装nginx,亲测有效

    系统平台:CentOS release 6.6 (Final) 64位. 一.安装编译工具及库文件 yum -y install make zlib zlib-devel gcc-c++ libtoo ...

  10. 安装mysql报错

    原文链接:https://blog.csdn.net/bao19901210/article/details/51917641 二进制安装 1.添加mysql组和mysql用户,用于设置mysql安装 ...