JAVA实现ZIP文件压缩和解压
前言
最近由于要和联通移动做接口对接,需求要求将文件压缩成ZIP格式,然后上传到移动和联通。最开始想到使用JDK原生API提供的ZipOutputStream做文件压缩,使用ZipFile将ZIP文件解压。网上提供了很多JAVA实现ZIP文件压缩和解压的案列,大多数都是采用JAVA原生API实现,但是当压缩和解压文件为中文名称时,原生API会出现中文文件压缩和解压失败的情况。而且网上提供的案例几乎都避开了压缩和解压中文文件这个坑,于是我开始找寻新的方案来解决JAVA压缩和解压中文文件。
一、JAVA原生ZipOutputStream无法压缩中文文件夹
在测试使用JAVA原生ZipOutputStream压缩文件时,对所有的英文文件压缩都没问题,但是当改为中文文件时,发现压缩的中文文件夹压缩失败了。这里我在initDatla文件下建了几个文件夹,其中包括一个中文的空文件夹叫空文件,当采用ZipOutPutStream压缩后,解压查看发现这个空文件压缩失败了。
压缩initData文件后得到的压缩文件ZipFile.zip。
解压zipFile.zip文件后,发现这个有中文文件夹压缩失败。
二、为什么JAVA原生API无法压缩中文文件夹
JAVA原生的ZIpOutputStream默认采用的编码是UTF-8,但是中文文件夹默认采用的编码是GBK,这导致了在压缩中文文件时会出现压缩失败或者乱码的情况。在ZipOutputStream的构造函数中已经写死了字符编码为UTF-8,这导致中文件夹压缩失败。
查看ZipOutputStream源码,发现并没有提供重置编码的方法,那就是说无法更改ZIpOutputStream的编码类型,这也是JDK原生API不支持中文文件夹压缩解压的原因。经过寻找发现,Apache的ant包中的压缩组件修复了这个问题,如果你在使用压缩功能时需要支持中文文件名,建议你直接使用Apache的压缩组件来实现这个功能。
三、采用ANT包实现文件压缩和解压(支持中文文件)
Apache的ant包中提供的ZipOutputStream解决了压缩中文文件失败和乱码的问题,在ZipOutPutStream中提供了setEncoding()方法,可以让我们设置编码类型。
(1)引入POM依赖
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.9.3</version>
</dependency>
使用ant包中的ZipOutPutStream类通过setEncoding("GBK")
方法和ZipFile构造函数ZipFile(File f, String encoding)
可以完美的解决中文文件问题。以下代码是经过本人多次测试写的工具类,可以直接运行并且有效。如有需要请直接复制代码即可。
/**
* @Author: Greyfus
* @Create: 2022-07-06 10:43
* @Version: 1.0.0
* @Description:compress the file into ZIP format,Unzip ZIP format
* PS:JDK原生的ZipOutInputStream默认编码为UTF8,而中文默认编码为GBK,如果对中文文件压缩可能出现压缩失败的情况,
* 采用org.apache.ant封装的ZipOutInputStream,通过setEncoding()方法可以解决这个问提。
* JDK原生的ZipFile默认编码为UTF8,使用org.apache.ant封装的ZipFile,通过new ZipFile(srcZipFile, "GBK")构造函数
* 可以解决中文文件解压后乱码的问题。
*/
package com.seeker.gzip;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Expand;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
public class ZIPFileUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(ZIPFileUtils.class);
private ZIPFileUtils() {
}
/**
* @param srcDir 源文件路径
* @param targetDir 压缩文件路径
* @param maintainStructure 压缩时是否按照源文件结构模式压缩
*/
public static void zipFile(String srcDir, String targetDir, boolean maintainStructure) {
checkParameters(srcDir, targetDir);
zipFile(new File(srcDir), new File(targetDir), maintainStructure);
}
/**
* @param srcDir 源文件
* @param targetDir 压缩文件
* @param maintainStructure 压缩时是否按照源文件结构模式压缩
*/
public static void zipFile(File srcDir, File targetDir, boolean maintainStructure) {
try {
zipFile(srcDir, new BufferedOutputStream(new FileOutputStream(targetDir)), maintainStructure);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public static void zipFile(File srcDir, OutputStream out, boolean maintainStructure) {
if (!srcDir.exists()) {
throw new RuntimeException(ZIPFileEnum.SOURCE_FILE_NOT_EXISTS.getMessage());
}
try (ZipOutputStream zos = new ZipOutputStream(out)) {
/**
* JDK原生的ZipOutputStream默认的是编码是UTF,然而中文编码是GBK,导致原生的JDK提供的API有时候无法压缩中文文件夹
*
*/
long startTime = System.currentTimeMillis();
zos.setEncoding("GBK");
compress(srcDir, zos, srcDir.getName(), maintainStructure);
LOGGER.info("ZIP files spend [{}] millisecond", System.currentTimeMillis() - startTime);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 压缩文件具体流程
*
* @param srcDir
* @param zos
* @param entryName 记录每个文件的路径
* @param maintainStructure
*/
private static void compress(File srcDir, ZipOutputStream zos, String entryName, boolean maintainStructure) {
if (srcDir.isFile()) {
try (FileInputStream in = new FileInputStream(srcDir)) {
zos.putNextEntry(new ZipEntry(entryName));
int len;
byte[] bytes = new byte[1024];
while ((len = in.read(bytes)) != -1) {
zos.write(bytes, 0, len);
}
zos.closeEntry();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (srcDir.isDirectory()) {
File[] listFiles = srcDir.listFiles();
if (ArrayUtils.isEmpty(listFiles)) {
/**
* 处理空文件夹压缩问题,JDK原生API无法处理中文件夹压缩
*/
try {
zos.putNextEntry(new ZipEntry(entryName + "/"));
zos.closeEntry();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
for (File srcFile : listFiles) {
if (maintainStructure) {
compress(srcFile, zos, entryName + File.separator + srcFile.getName(), maintainStructure);
} else {
compress(srcFile, zos, srcFile.getName(), maintainStructure);
}
}
}
}
/**
* 解压文件
*
* @param srcZipFile zip文件路径
* @param targetFile 解压后的存储地址
*/
public static void unzipFile(String srcZipFile, String targetFile) {
checkParameters(srcZipFile, targetFile);
unzipFile(new File(srcZipFile), targetFile);
}
/**
* 解压文件
*
* @param srcZipFile zip文件
* @param targetDir 解压后的存储地址
*/
public static void unzipFile(File srcZipFile, String targetDir) {
if (!srcZipFile.exists()) {
throw new RuntimeException(ZIPFileEnum.SOURCE_FILE_NOT_EXISTS.getMessage());
}
Project project = new Project();
Expand expand = new Expand();
expand.setProject(project);
expand.setSrc(srcZipFile);
expand.setOverwrite(true);
File file = new File(targetDir);
if (!file.exists()) {
file.mkdirs();
}
expand.setDest(file);
expand.execute();
}
private static void checkParameters(String srcDir, String targetDir) {
if (StringUtils.isEmpty(srcDir)) {
throw new RuntimeException(ZIPFileEnum.SOURCE_FILE_IS_EMPTY.getMessage());
}
if (StringUtils.isEmpty(targetDir)) {
throw new RuntimeException(ZIPFileEnum.TARGET_FILE_IS_EMPTY.getMessage());
}
if (!new File(srcDir).exists()) {
throw new RuntimeException(ZIPFileEnum.SOURCE_FILE_NOT_EXISTS.getMessage());
}
}
private enum ZIPFileEnum {
SOURCE_FILE_IS_EMPTY("0001", "Source file is empty!"),
TARGET_FILE_IS_EMPTY("0002", "Target file is Empty !"),
SOURCE_FILE_NOT_EXISTS("0003", "Source file not exists");
private String code;
private String message;
ZIPFileEnum(String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
}
}
(2)测试压缩和解压中文文件效果
ZIPFileUtils工具类有只有两个方法,一个是zipFile方法用于压缩文件,一个是unzipFile方法用于解压文件。
zipFile方法要多个重载方法,可以接收不同的参数,更多的参数使用请查看源码。
这里做一个演示,将E:/app/initData目录的文件压缩,压缩后的文件路径为E:/app/zipFile.zip,并且保持源文件目录结构。
压缩完成后查看压缩文件是否生成,并解压zipFile.zip文件,查看中文文件是否压缩成功。
解压zipFile.zip查看压缩效果。
对于unzipFile方法解压文件就不在叙述了,因为比较简单。如果有任何疑问或者意见可以留言。