一 前言

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

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

一 压缩文件

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

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

1.1 压缩多个文件

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

    /**
* 压缩多个文件,压缩后的所有文件在同一目录下
*
* @param zipFileName 压缩后的文件名
* @param files 需要压缩的文件列表
* @throws IOException IO异常
*/
public static void zipMultipleFiles(String zipFileName, File... files) throws IOException {
ZipOutputStream zipOutputStream = null;
try {
// 输出流
zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFileName));
// 遍历每一个文件,进行输出
for (File file : files) {
zipOutputStream.putNextEntry(new ZipEntry(file.getName()));
FileInputStream fileInputStream = new FileInputStream(file);
int readLen;
byte[] buffer = new byte[1024];
while ((readLen = fileInputStream.read(buffer)) != -1) {
zipOutputStream.write(buffer, 0, readLen);
}
// 关闭流
fileInputStream.close();
zipOutputStream.closeEntry();
}
} finally {
if (null != zipOutputStream) {
try {
zipOutputStream.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}

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

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

1.2 压缩文件或文件树

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

package com.nobody.zip;

import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream; public class ZipUtils { /**
* 压缩文件或文件夹(包括所有子目录文件)
*
* @param sourceFile 源文件
* @param format 格式(zip或rar)
* @throws IOException 异常信息
*/
public static void zipFileTree(File sourceFile, String format) throws IOException {
ZipOutputStream zipOutputStream = null;
try {
String zipFileName;
if (sourceFile.isDirectory()) { // 目录
zipFileName = sourceFile.getParent() + File.separator + sourceFile.getName() + "."
+ format;
} else { // 单个文件
zipFileName = sourceFile.getParent()
+ sourceFile.getName().substring(0, sourceFile.getName().lastIndexOf("."))
+ "." + format;
}
// 压缩输出流
zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFileName));
zip(sourceFile, zipOutputStream, "");
} finally {
if (null != zipOutputStream) {
// 关闭流
try {
zipOutputStream.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
} /**
* 递归压缩文件
*
* @param file 当前文件
* @param zipOutputStream 压缩输出流
* @param relativePath 相对路径
* @throws IOException IO异常
*/
private static void zip(File file, ZipOutputStream zipOutputStream, String relativePath)
throws IOException { FileInputStream fileInputStream = null;
try {
if (file.isDirectory()) { // 当前为文件夹
// 当前文件夹下的所有文件
File[] list = file.listFiles();
if (null != list) {
// 计算当前的相对路径
relativePath += (relativePath.length() == 0 ? "" : "/") + file.getName();
// 递归压缩每个文件
for (File f : list) {
zip(f, zipOutputStream, relativePath);
}
}
} else { // 压缩文件
// 计算文件的相对路径
relativePath += (relativePath.length() == 0 ? "" : "/") + file.getName();
// 写入单个文件
zipOutputStream.putNextEntry(new ZipEntry(relativePath));
fileInputStream = new FileInputStream(file);
int readLen;
byte[] buffer = new byte[1024];
while ((readLen = fileInputStream.read(buffer)) != -1) {
zipOutputStream.write(buffer, 0, readLen);
}
zipOutputStream.closeEntry();
}
} finally {
// 关闭流
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
} public static void main(String[] args) throws Exception {
String path = "D:/test";
String format = "zip";
zipFileTree(new File(path), format);
}
}

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

1.3 借助文件访问器压缩

还有一种更简单的方式,我们不自己写递归遍历。借助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

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. 数理统计9:完备统计量,指数族,充分完备统计量法,CR不等式

    昨天我们给出了统计量是UMVUE的一个必要条件:它是充分统计量的函数,且是无偏估计,但这并非充分条件.如果说一个统计量的无偏估计函数一定是UMVUE,那么它还应当具有完备性的条件,这就是我们今天将探讨 ...

  2. Kubernets二进制安装(2)之Bind9安装

    1.修改主机名 hostnamectl set-hostname mfyxw10 hostnamectl set-hostname mfyxw20 hostnamectl set-hostname m ...

  3. oslab oranges 一个操作系统的实现 final

    见 github  https://github.com/TouwaErioH/subjects/tree/master/oslab-oranges

  4. SQL优化汇总

    今天面某家公司,然后问我SQL优化,感觉有点忘了,今天特此总结一下: 总结得是分两方面:索引优化和查询优化: 一. 索引优化: 1. 独立的列 在进行查询时,索引列不能是表达式的一部分,也不能是函数的 ...

  5. HDU 4746 Mophues(莫比乌斯反演)题解

    题意: \(Q\leq5000\)次询问,每次问你有多少对\((x,y)\)满足\(x\in[1,n],y\in[1,m]\)且\(gcd(x,y)\)的质因数分解个数小于等于\(p\).\(n,m, ...

  6. LOJ6285 数列分块入门9(分块 区间众数)题解

    题意:给出区间内的最小众数 思路:分块,离散化每个数,开vector记录每个数p出现的位置,这样就能二分出L,R以内p的个数了.众数有一个性质,用mode(a)表示集合a的众数,那么mode(a∪b) ...

  7. React Hooks: useEffect All In One

    React Hooks: useEffect All In One useEffect https://reactjs.org/docs/hooks-effect.html https://react ...

  8. Flutter & release an iOS app

    Flutter & release an iOS app https://flutter.dev/docs/deployment/ios .ipa https://en.wikipedia.o ...

  9. Apple & HTML5 app

    Apple & HTML5 app https://developer.apple.com/cn/news/?id=09062019b https://developer.apple.com/ ...

  10. PAUL ADAMS ARCHITECT:薪资追不上房价美一半家庭难买房

    尽管上一年度美国经济遭受重创,但美国房价依旧持续蹿扬,据最新调查显示,美国大部分地区的房价已经到了一般家庭无法负担的水准. 美国房价上涨持续强劲,主要受益美国人居家办公需求(受大环境影响,目前美国有7 ...