Java中的各种文件压缩、解压方法(更新中~)

Note:本文的工具都开源在GitHub的JUtils上。

ZIP压缩

zipOutputStream方式

压缩

这个方式是最基础的方式,是Java底层给出的一种流方案。

其压缩方式步骤如下:

  1. 新建一个ZipOutputStream,其参数接收一个OutputStream,一般是新建一个FileOutputStream。
  2. 循环获取要压缩的文件/文件夹,调用putEntry函数进行流的写入。
  3. putEntry的过程如下:
    1. 如果是文件夹,则将其设为基础目录,然后递归调用putEntry来压缩该文件夹下的文件。
    2. 如果是文件,则使用基础目录拼接后将该entry放入流中。
  4. 设置压缩级别。
  5. flush、close这个流。

值得注意的有:

  • 文件夹的处理,即递归的获取子文件夹,然后进行压缩。
  • entry的名字则自带了文件夹目录层级,即xxx/aaaa.jpg则会在压缩包中新建一个xxx的文件夹,并把aaa.jpg放入其中。

具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/***
* @Author MichaelWang
* @Date 2022/7/4
* @Description fileUrls中,文件只会取文件名,文件夹则只会取最内层文件夹的名字。但子文件夹和其下的子文件就会递归压缩了。
* @Param fileInfos:
* @Param compressionLevel:
* @Param filePath:
* @Return void
* @Version 1.0.0
**/
public static void zipFilesByZipStream(ArrayList<String> fileUrls, Integer compressionLevel, String filePath) throws IOException {
// 判断外围文件夹是否存在,如果不存在则创建
if(fileUrls == null || filePath.length() == 0){
throw new RuntimeException("文件列表不能为空!");
}
String filePathWithoutName = FileUtils.getOuterFolderPath(filePath);
File pathFile = new File(filePathWithoutName);
if(!pathFile.exists()){
pathFile.mkdirs();
}

// 判断源文件存在,则删除
File tempFile = new File(filePath);
if(tempFile.exists()){
tempFile.delete();
}

ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(filePath), Charset.forName("GBK"));
for (String url : fileUrls) {
File file = new File(url);
putEntry(zipOutputStream, file, "");
}
zipOutputStream.setLevel(compressionLevel);
zipOutputStream.flush();
zipOutputStream.close();
};

private static void putEntry(ZipOutputStream zipOutputStream, File file, String basePath) throws IOException {
if (file.isDirectory()){
File[] files = file.listFiles();
for (File subFile : files) {
putEntry(zipOutputStream, subFile, basePath + File.separator + file.getName());
}
}else{
ZipEntry entry = new ZipEntry(basePath + File.separator + file.getName());
entry.setSize(file.length());
zipOutputStream.putNextEntry(entry);
byte[] fileByte = new byte[(int) file.length()];
FileInputStream fileInputStream = new FileInputStream(file);
fileInputStream.read(fileByte);
zipOutputStream.write(fileByte);
zipOutputStream.flush();
fileInputStream.close();
}
}

解压

解压的过程就比较简单了,因为不涉及递归获取文件。其过程如下:

  1. 只需要不断调用getNextEntry方法,获取entry,判断其为文件夹还是文件:
    1. 如果是文件夹,则结合基础目录,拼接新目录并新建该文件夹。
    2. 如果是文件,则结合基础目录,拼接新文件目录,然后读取流中的文件并写入该文件。
  2. 关闭Entry、关闭流。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public static void extractZipByStream(String zipPath) throws IOException {
File zipFile = new File(zipPath);
String newOuterPath = zipFile.getParent() + File.separator + zipFile.getName().split("\\.")[0];
File newOuterFolder = new File(newOuterPath);
if(!newOuterFolder.exists()){
newOuterFolder.mkdirs();
}
ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipPath), Charset.forName("GBK"));
ZipEntry entry = zipInputStream.getNextEntry();
while(entry != null){
String newPath = newOuterPath + File.separator + entry.getName();
File file = new File(newPath);
if(entry.isDirectory()){
file.mkdirs();
//文件
}else{
String parent = file.getParent();
File outerFolder = new File(parent);
if (!outerFolder.exists()){
outerFolder.mkdirs();
}
file.createNewFile();
byte[] buff = new byte[1024];
BufferedOutputStream bufferedInputStream = new BufferedOutputStream(new FileOutputStream(file));
int len;
while ((len = zipInputStream.read(buff, 0 ,1024)) != -1) {
bufferedInputStream.write(buff, 0, len);
}
zipInputStream.read(buff);
bufferedInputStream.flush();
bufferedInputStream.close();
}
entry = zipInputStream.getNextEntry();
}
zipInputStream.closeEntry();
zipInputStream.close();
}

遇到的坑

  1. entry.getSize():这个接口返回对应entry未压缩时的大小。但是其可能是未知的,所以不能通过该属性来新建byte数组。

  2. 文件流读取:就像上面说的,entry.getSize()可能是未知的。所以流读取的时候,应当采用先初始化一个固定大小的数组,然后调用read时传入该数组的大小,这里是1024。但是read会返回当次读取的字节长度,如果小于1024,则返回该次的大小,否则就是传入的大小(1024)。

  3. 还得注意中文,如果文件中含有中文,那必须在新建流的时候,传入第二个编码参数。

    1
    2
    3
    ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipPath), Charset.forName("GBK"));

    ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipPath), Charset.forName("GBK"));

ZipFile方式

ZipFile类可以用来读取zip文件,其实本质上还是利用ZipInputStream,所以其只可以用来解压缩文件。但是由于其对压缩包的每个文件都进行拆解,所以相较于直接用ZipInputStream,会简单一点(但也只是一点)。

ZipFile有一个entries()方法,可以获取所有的元素。然后遍历整个所有的entries。与ZipInputStream不同的就是,可以通过entries对象获取单个inputStream,就不存在自己看分割长度的问题。

具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/***
* @Author MichaelWang
* @Date 2022/7/6
* @Description 通过Java自带的ZipFile类实现文件解压缩
* @Param zipPath: 压缩文件目标地址
* @Return void
* @Version 1.0.0
**/
public static void extractZipFileByZipFile(String zipPath) throws IOException {
File NativeZipFile = new File(zipPath);
String newOuterPath = NativeZipFile.getParent() + File.separator + NativeZipFile.getName().split("\\.")[0];
ZipFile zipFile = new ZipFile(zipPath);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
for (Iterator<? extends ZipEntry> it = entries.asIterator(); it.hasNext(); ) {
ZipEntry zipEntry = it.next();
String newPath = newOuterPath + File.separator + zipEntry.getName();
File file = new File(newPath);
if(zipEntry.isDirectory()){
file.mkdirs();
//文件
}else{
String parent = file.getParent();
File outerFolder = new File(parent);
if (!outerFolder.exists()){
outerFolder.mkdirs();
}
file.createNewFile();
InputStream inputStream = zipFile.getInputStream(zipEntry);
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(inputStream.readAllBytes());
fileOutputStream.flush();
fileOutputStream.close();
}
}
}

zip4j方式

Zip4j是用于zip文件或流的最全面的Java库。最重要的是:zip4j是唯一一个支持压缩文件加密的工具。像上面提到的ZipOutputStream并不支持加密。并且zip4j的API很简单。如果不是,一般就不需要封装了,可以直接用。

下面还是给出一个简单的封装,主要是将其功能封装在了多个重载函数中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/***
* @Author MichaelWang
* @Date 2022/7/6
* @Description 通过zip4j进行无密码的压缩
* @Param zipPath: 压缩文件目标地址
* @Param fileUrls: 被压缩文件的列表
* @Param compressLevel: 压缩率
* @Return void
* @Version 1.0.0
**/
public static void compressZipFileByZip4j(String zipPath, ArrayList<String> fileUrls, CompressionLevel compressLevel) throws ZipException {
net.lingala.zip4j.ZipFile zipFile = new net.lingala.zip4j.ZipFile(zipPath);
_compressZipFileByZip4j(zipFile, fileUrls, compressLevel);
}

/***
* @Author MichaelWang
* @Date 2022/7/6
* @Description 通过zip4j进行有密码压缩
* @Param zipPath: 压缩文件目标的地址
* @Param fileUrls: 要被压缩的文件列表
* @Param compressLevel: 压缩率
* @Param password: 密码
* @Return void
* @Version 1.0.0
**/
public static void compressZipFileByZip4j(String zipPath, ArrayList<String> fileUrls, CompressionLevel compressLevel, @NotNull String password) throws ZipException {
net.lingala.zip4j.ZipFile zipFile = new net.lingala.zip4j.ZipFile(zipPath, password.toCharArray());
_compressZipFileByZip4j(zipFile, fileUrls, compressLevel);
}

/***
* @Author MichaelWang
* @Date 2022/7/6
* @Description 通过zip4j进行无密码压缩,然后重命名被压缩的文件
* @Param zipPath: 压缩文件目标的地址
* @Param fileUrls: 要被压缩的文件列表
* @Param compressLevel: 压缩率
* @Param renames: 重命名map,<originalName, newName>
* @Return void
* @Version 1.0.0
**/
public static void compressZipFileByZip4j(String zipPath, ArrayList<String> fileUrls, CompressionLevel compressLevel, Map<String, String> renames) throws ZipException {
net.lingala.zip4j.ZipFile zipFile = new net.lingala.zip4j.ZipFile(zipPath);
_compressZipFileByZip4j(zipFile, fileUrls, compressLevel);
zipFile.renameFiles(renames);
}

/***
* @Author MichaelWang
* @Date 2022/7/6 通过zip4j进行有密码压缩,然后重命名被压缩的文件
* @Description
* @Param zipPath: 压缩文件目标的地址
* @Param fileUrls: 要被压缩的文件列表
* @Param compressLevel: 压缩率
* @Param password: 密码
* @Param renames: 重命名map,<originalName, newName>
* @Return void
* @Version 1.0.0
**/
public static void compressZipFileByZip4j(String zipPath, ArrayList<String> fileUrls, CompressionLevel compressLevel, @NotNull String password, Map<String, String> renames) throws ZipException {
net.lingala.zip4j.ZipFile zipFile = new net.lingala.zip4j.ZipFile(zipPath, password.toCharArray());
_compressZipFileByZip4j(zipFile, fileUrls, compressLevel);
zipFile.renameFiles(renames);
}

/***
* @Author MichaelWang
* @Date 2022/7/6
* @Description 通过zip4j进行无密码解压
* @Param zipPath: 压缩文件目标的地址
* @Param targetPath: 解压释放目标地址
* @Return void
* @Version 1.0.0
**/
public static void extractZipFileByZip4j(String zipPath, String targetPath) throws ZipException {
net.lingala.zip4j.ZipFile zipFile = new net.lingala.zip4j.ZipFile(zipPath);
zipFile.extractAll(targetPath);
}

/***
* @Author MichaelWang
* @Date 2022/7/6
* @Description 通过zip4j进行有密码解压
* @Param zipPath: 压缩文件目标的地址
* @Param targetPath: 解压释放目标地址
* @Param password: 密码
* @Return void
* @Version 1.0.0
**/
public static void extractZipFileByZip4j(String zipPath, String targetPath, @NotNull String password) throws ZipException {
net.lingala.zip4j.ZipFile zipFile = new net.lingala.zip4j.ZipFile(zipPath, password.toCharArray());
zipFile.extractAll(targetPath);
}

/***
* @Author MichaelWang
* @Date 2022/7/6
* @Description 通过zip4j进行无密码解压,并且只解压给出列表的文件
* @Param zipPath: 压缩文件目标的地址
* @Param targetPath: 解压释放目标地址
* @Param extractFiles: 要解压的地址
* @Return void
* @Version 1.0.0
**/
public static void extractZipFileByZip4j(String zipPath, String targetPath, List<String> extractFiles) throws ZipException {
net.lingala.zip4j.ZipFile zipFile = new net.lingala.zip4j.ZipFile(zipPath);
for (String extractFile : extractFiles) {
zipFile.extractFile(extractFile, targetPath);
}
}

/***
* @Author MichaelWang
* @Date 2022/7/6
* @Description 通过zip4j进行有密码解压,并且只解压给出列表的文件
* @Param zipPath: 压缩文件目标的地址
* @Param targetPath: 解压释放目标地址
* @Param password: 密码
* @Param extractFiles: 要解压的地址
* @Return void
* @Version 1.0.0
**/
public static void extractZipFileByZip4j(String zipPath, String targetPath, @NotNull String password, List<String> extractFiles) throws ZipException {
net.lingala.zip4j.ZipFile zipFile = new net.lingala.zip4j.ZipFile(zipPath, password.toCharArray());
for (String extractFile : extractFiles) {
zipFile.extractFile(extractFile, targetPath);
}
}

Powered by Hexo and Hexo-theme-hiker

Copyright © 2019 - 2024 My Wonderland All Rights Reserved.

UV : | PV :