还有一种更简单的方式,我们不自己写递归遍历。借助Java原生类,SimpleFileVisitor,它提供了几个访问文件的方法,其中有个方法visitFile,对于文件树中的每一个文件(文件夹除外),都会调用这个方法。我们只要写一个类继承SimpleFileVisitor,然后重写visitFile方法,实现将每一个文件写入到压缩文件中即可。
当然,除了visitFile方法,它里面还有preVisitDirectory,postVisitDirectory,visitFileFailed等方法,通过方法名大家也猜出什么意思了。
package com.nobody.zip; import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.*; import 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 */ public class ZipFileTree extends SimpleFileVisitor<Path> { // zip输出流 private ZipOutputStream zipOutputStream; // 源目录 private Path sourcePath; public ZipFileTree() {} /** * 压缩目录以及所有子目录文件 * * @param sourceDir 源目录 */ public void zipFile(String sourceDir) throws IOException { try { // 压缩后的文件和源目录在同一目录下 String zipFileName = sourceDir + ".zip"; this.zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFileName)); this.sourcePath = Paths.get(sourceDir); // 开始遍历文件树 Files.walkFileTree(sourcePath, this); } finally { // 关闭流 if (null != zipOutputStream) { zipOutputStream.close(); } } } // 遍历到的每一个文件都会执行此方法 @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) throws IOException { // 取相对路径 Path targetFile = sourcePath.relativize(file); // 写入单个文件 zipOutputStream.putNextEntry(new ZipEntry(targetFile.toString())); byte[] bytes = Files.readAllBytes(file); zipOutputStream.write(bytes, 0, bytes.length); zipOutputStream.closeEntry(); // 继续遍历 return FileVisitResult.CONTINUE; } // 遍历每一个目录时都会调用的方法 @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { return super.preVisitDirectory(dir, attrs); } // 遍历完一个目录下的所有文件后,再调用这个目录的方法 @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { return super.postVisitDirectory(dir, exc); } // 遍历文件失败后调用的方法 @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return super.visitFileFailed(file, exc); } public static void main(String[] args) throws IOException { // 需要压缩源目录 String sourceDir = "D:/test"; // 压缩 new ZipFileTree().zipFile(sourceDir); } } 三 解压文件解压压缩包,借助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 */ public static void unzip(String zipFilePath, String desDirectory) throws Exception { File desDir = new File(desDirectory); if (!desDir.exists()) { boolean mkdirSuccess = desDir.mkdir(); if (!mkdirSuccess) { throw new Exception("创建解压目标文件夹失败"); } } // 读入流 ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipFilePath)); // 遍历每一个文件 ZipEntry zipEntry = zipInputStream.getNextEntry(); while (zipEntry != null) { if (zipEntry.isDirectory()) { // 文件夹 String unzipFilePath = desDirectory + File.separator + zipEntry.getName(); // 直接创建 mkdir(new File(unzipFilePath)); } else { // 文件 String unzipFilePath = desDirectory + File.separator + zipEntry.getName(); File file = new File(unzipFilePath); // 创建父目录 mkdir(file.getParentFile()); // 写出文件流 BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(unzipFilePath)); byte[] bytes = new byte[1024]; int readLen; while ((readLen = zipInputStream.read(bytes)) != -1) { bufferedOutputStream.write(bytes, 0, readLen); } bufferedOutputStream.close(); } zipInputStream.closeEntry(); zipEntry = zipInputStream.getNextEntry(); } zipInputStream.close(); } // 如果父目录不存在则创建 private static void mkdir(File file) { if (null == file || file.exists()) { return; } mkdir(file.getParentFile()); file.mkdir(); } public static void main(String[] args) throws Exception { String zipFilePath = "D:/test.zip"; String desDirectory = "D:/a"; unzip(zipFilePath, desDirectory); } } 四 总结在解压缩文件过程中,主要是对流的读取操作,注意进行异常处理,以及关闭流。
web应用中,通过接口可以实现文件上传下载,对应的我们只要把压缩后的文件,写入到response.getOutputStream()输出流即可。
解压缩文件时,注意空文件夹的处理。
此演示项目已上传到Github,如有需要可自行下载,欢迎 Star 。 https://github.com/LucioChn/common-utils