0%

使用zip4j打包文件

​ 最近接到需求需要为打包后的zip文件设置压缩密码,而我们之前使用的ZipOutputStream结合hutoolNoiUtil进行打包生成zip文件,但是ZipOutputStream没有设置解压密码的功能,故尝试寻找其他三方库(这里是直接问chatGPT),它的回答是Zip4j

介绍

Zip4j是一个用于处理ZIP文件格式的Java库,它提供了一个易于使用的API,可以方便地创建、提取和修改ZIP文件,以及支持密码保护的ZIP文件。

Zip4j的主要特点包括:

  1. 支持标准ZIP文件格式以及ZIP64扩展格式,可以处理包含数百万文件的ZIP文件。
  2. 支持密码保护的ZIP文件,包括标准密码和AES加密密码。
  3. 支持ZIP文件的分卷(spanned)和自解压缩(self-extracting)功能。
  4. 支持ZIP文件的压缩级别、注释和UTF-8编码文件名等属性的设置。
  5. 提供了方便的API,可以方便地添加、提取和修改ZIP文件中的文件和文件夹,以及获取ZIP文件中的文件列表和属性信息。

使用

1
2
3
4
5
<dependency>
<groupId>net.lingala.zip4j</groupId>
<artifactId>zip4j</artifactId>
<version>2.6.1</version>
</dependency>

我使用的是2.1.6版本,新版本对旧版本中的一些常量和接口有调整。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void firstDemo(File file1, File file2) throws ZipException {
List<File> fileToAdds = Arrays.asList(file1, file2);

ZipFile zipFile = new ZipFile("/xxx/result.zip");
zipFile.addFiles(fileToAdds);
}

public static void main(String[] args) throws ZipException {
String filePath1 = "/xxx/demo1.txt";
String filePath2 = "/xxx/demo2.txt";

File file1 = new File(filePath1);
File file2 = new File(filePath2);

ZipDemo zipDemo = new ZipDemo();
zipDemo.firstDemo(file1, file2);
}

fileToAdds列表打包为目标地址为/xxx/result.zip的压缩文件。这种写法是最简单的打包方式,将需要打包的源文件都打包在目标zip文件的顶层目录下。

1
2
3
result.zip
├─────── demo1.txt
└─────── demo2.txt

但是有时候,甚至大部分时候都是需要将打包源文件进行分级(压缩包内文件夹)的方式进行打包。

1
2
3
4
result.zip
├───── dir1
│ └─ demo1.txt
└───── demo2.txt

1.设置目标zip文件内文件夹结构示例代码:

1
2
3
4
5
6
7
8
9
10
11
public void customInnerFilePath(File file1, File file2) throws ZipException {
// 初始化压缩参数
ZipParameters zipParameters = new ZipParameters();
// 指定zip文件内部对于根目录的绝对路径
zipParameters.setFileNameInZip("dir1/demo1.txt");

ZipFile zipFile = new ZipFile("/xxx/result.zip");
// 将设置的压缩参数作用在某个文件上
zipFile.addFile(file1, zipParameters);
zipFile.addFile(file2);
}

2.设置文件夹加密:

1
2
3
4
5
6
7
8
9
10
11
12
public void encryptZip(File file1, File file2) throws ZipException {
ZipParameters zipParameters = new ZipParameters();
// 指定zip文件内部对于根目录的绝对路径
zipParameters.setFileNameInZip("dir1/demo1.txt");
zipParameters.setEncryptFiles(true);
zipParameters.setEncryptionMethod(EncryptionMethod.AES);
zipParameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_256);

ZipFile zipFile = new ZipFile("/xxx/result.zip", "123456".toCharArray());
zipFile.addFile(file1, zipParameters);
zipFile.addFile(file2);
}

注意:上述代码中仅仅是对file1设置了zipParameters参数,也就是说现在有两个被打包文件,但是压缩参数只设置到了其中的一个文件上,打开压缩后的文件会发现,是需要解压密码才能解压,但是如果你有一些不需要解压可以查看zip包内部情况的工具可以发现,可以file2所对应的文件是无需密码就可以查看,也就是说,如果要对压缩包内所有文件在没有解压密码之前都不能访问,需要每个文件添加时设置zipParameters参数,设置为加密开启。

zip文件内部分文件加密.png

还有一点:踩过一个坑,由于我在打包文件的时候,可能出现某一个压缩包已经打包过了,但是业务上并不知道已经打包过了,所以会出现重复打包的情况,而我在针对重复打包的情况,会先将目标zip设置为一个空文件。

1
2
// 通过java标准库中的方法,这时这个目标文件存在,但是内容被清除,结果是0B
Files.newByteChannel(result, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE).close();

但是这种情况下运行会报错:

Zip file size less than minimum expected zip file size. Probably not a zip file or a corrupted zip file

1
2
3
4
if (zip4jRaf.length() < ENDHDR) {
throw new ZipException("Zip file size less than minimum expected zip file size. " +
"Probably not a zip file or a corrupted zip file");
}

大概意思是目标文件是一个空文件,校验了目标文件头部的一个什么长度。没有细究这个问题,我的做法是改成直接删除旧文件,然后再生成文件。

3.打包分卷

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static List<File> zip(List<String> srcFiles, String destFile, long fileSize) throws ZipException {
ZipFile zipFile = new ZipFile(destFile);
List<File> filesToAdd = srcFiles.stream().map(File::new).collect(Collectors.toList());

ZipParameters parameters = new ZipParameters();
parameters.setCompressionMethod(CompressionMethod.DEFLATE);
parameters.setCompressionLevel(CompressionLevel.NORMAL);

//未指定则设置为512KB
if(fileSize==0){
fileSize = 65536;
}
//设置压缩分卷
zipFile.createSplitZipFile(filesToAdd, parameters, true, fileSize);
return zipFile.getSplitZipFiles();
}

通过分卷生成的文件结构为:

1
2
3
目标文件目录
├─────── result.z01
└─────── result.z02

这里会生成.z+数字排列的压缩包,要提取或打开这样的分卷ZIP文件,必须将所有的ZIP文件都放在同一个目录下。这种场景就可以用于大文件分卷为多个小文件后,传输小文件到目标地址。

4.提取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void get() throws ZipException{
// 创建一个ZipFile对象,用于打开要提取的ZIP文件
ZipFile zipFile = new ZipFile("/xxx/result.zip");
// 设置解压缩密码(如果需要)
if (zipFile.isEncrypted()) {
zipFile.setPassword("password".toCharArray());
}
// 1.将所有文件都提取到当前目录下
zipFile.extractAll("./");
// 2.仅提取一个文件
zipFile.extractFile("demo1.txt", "./");
// 3.提取的这个文件不是在顶层目录
zipFile.extractFile("dir3/demo3.txt", "./");
// 4.提取整个内部目录
zipFile.extractFile("dir3/", "./");
}

总之,Zip4j是一个非常实用的Java库,可以帮助程序员方便地处理ZIP文件格式,节省大量的开发时间和工作量。还包含其他的一些,比如添加某个文件到zip文件中,修改zip中的某个文件等等。

-------------本文结束感谢您的阅读-------------