目錄
Apache Commons Compress 概述
1、Apache Commons Compress 官網:http://commons.apache.org/proper/commons-compress/index.html
2、Apache Commons Compress 庫定義了一個用於處理 ar,cpio,Unix 轉儲,tar,zip,gzip,XZ,Pack200,bzip2、7z,arj,lzma,snappy,DEFLATE,lz4,Brotli,Zstandard,DEFLATE64 和 Z 文件的 API 。
3、當前 Compress 版本是 1.19,並且需要 Java 7 及以上支持。
現在 ParallelScatterZipCreator 會按照條目添加到存檔的順序來寫入條目。
現在默認情況下解析額外的字段時 ZipArchiveInputStream和ZipFile 更具寬容性。
TarArchiveInputStream 具有新的寬鬆模式,該模式可能允許它讀取某些已損壞的檔案。
4、官網用戶使用手冊提供了各種壓縮格式的處理方式(本文僅以常用的 zip 格式爲例進行介紹):http://commons.apache.org/proper/commons-compress/examples.html
5、Apache Commons Compress 官網下載:http://commons.apache.org/proper/commons-compress/download_compress.cgi
6、可以從 Maven 中央倉庫獲取依賴:https://mvnrepository.com/artifact/org.apache.commons/commons-compress
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-compress -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.19</version>
</dependency>
核心 API
1)壓縮輸入流,用於解壓壓縮文件:public abstract class ArchiveInputStream extends java.io.InputStream
2)壓縮輸處出流,用於壓縮壓縮文件:public abstract class ArchiveOutputStream extends java.io.OutputStream
3)壓縮文件內部存檔的條目,壓縮文件內部的每一個被壓縮文件都稱爲一個條目:public interface ArchiveEntry
本地文件解壓縮代碼示例
1、生產中常見的需求之一就是對服務器上的某些文件進行壓縮,或者解壓。本文以常用的 zip 格式爲例進行介紹。
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.zip.Zip64Mode;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import java.io.*;
/**
* 壓縮工具類
*
* @author helloworld
*/
public class ZipUtil {
/**
* 將文件打包成 zip 壓縮包文件
*
* @param sourceFiles 待壓縮的多個文件列表。只支持文件,不能有目錄,否則拋異常。
* @param zipFile 壓縮文件。文件可以不存在,但是目錄必須存在,否則拋異常。如 C:\Users\Think\Desktop\aa.zip
* @param isDeleteSourceFile 是否刪除源文件(sourceFiles)
* @return 是否壓縮成功
*/
public static boolean archiveFiles2Zip(File[] sourceFiles, File zipFile, boolean isDeleteSourceFile) {
InputStream inputStream = null;//源文件輸入流
ZipArchiveOutputStream zipArchiveOutputStream = null;//壓縮文件輸出流
if (sourceFiles == null || sourceFiles.length <= 0) {
return false;
}
try {
zipArchiveOutputStream = new ZipArchiveOutputStream(zipFile);//ZipArchiveOutputStream(File file) :根據文件構建壓縮輸出流,將源文件壓縮到此文件.
//setUseZip64(final Zip64Mode mode):是否使用 Zip64 擴展。
// Zip64Mode 枚舉有 3 個值:Always:對所有條目使用 Zip64 擴展、Never:不對任何條目使用Zip64擴展、AsNeeded:對需要的所有條目使用Zip64擴展
zipArchiveOutputStream.setUseZip64(Zip64Mode.AsNeeded);
for (File file : sourceFiles) {
//將每個源文件用 ZipArchiveEntry 實體封裝,然後添加到壓縮文件中. 這樣將來解壓后里面的文件名稱還是保持一致.
ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(file.getName());
zipArchiveOutputStream.putArchiveEntry(zipArchiveEntry);
inputStream = new FileInputStream(file);//獲取源文件輸入流
byte[] buffer = new byte[1024 * 5];
int length = -1;//每次讀取的字節大小。
while ((length = inputStream.read(buffer)) != -1) {
//把緩衝區的字節寫入到 ZipArchiveEntry
zipArchiveOutputStream.write(buffer, 0, length);
}
}
zipArchiveOutputStream.closeArchiveEntry();//寫入此條目的所有必要數據。如果條目未壓縮或壓縮後的大小超過4 GB 則拋出異常
zipArchiveOutputStream.finish();//壓縮結束.
if (isDeleteSourceFile) {//爲 true 則刪除源文件.
for (File file : sourceFiles) {
file.deleteOnExit();
}
}
} catch (IOException e) {
e.printStackTrace();
return false;
} finally {
//關閉輸入、輸出流,釋放資源.
try {
if (null != inputStream) {
inputStream.close();
}
if (null != zipArchiveOutputStream) {
zipArchiveOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
/**
* 將 zip 壓縮包解壓成文件到指定文件夾下
*
* @param zipFile 待解壓的壓縮文件。親測 .zip 文件有效;.7z 壓縮解壓時拋異常。
* @param targetDir 解壓後文件存放的目的地. 此目錄必須存在,否則異常。
* @return 是否成功
*/
public static boolean decompressZip2Files(File zipFile, File targetDir) {
InputStream inputStream = null;//源文件輸入流,用於構建 ZipArchiveInputStream
OutputStream outputStream = null;//解壓縮的文件輸出流
ZipArchiveInputStream zipArchiveInputStream = null;//zip 文件輸入流
ArchiveEntry archiveEntry = null;//壓縮文件實體.
try {
inputStream = new FileInputStream(zipFile);//創建輸入流,然後轉壓縮文件輸入流
zipArchiveInputStream = new ZipArchiveInputStream(inputStream, "UTF-8");
//遍歷解壓每一個文件.
while (null != (archiveEntry = zipArchiveInputStream.getNextEntry())) {
String archiveEntryFileName = archiveEntry.getName();//獲取文件名
File entryFile = new File(targetDir, archiveEntryFileName);//把解壓出來的文件寫到指定路徑
byte[] buffer = new byte[1024 * 5];
outputStream = new FileOutputStream(entryFile);
int length = -1;
while ((length = zipArchiveInputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
outputStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
return false;
} finally {
try {
if (null != outputStream) {
outputStream.close();
}
if (null != zipArchiveInputStream) {
zipArchiveInputStream.close();
}
if (null != inputStream) {
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
}
文件壓縮並提供網絡下載
1、生產中另一個常見的需求是,用戶下載的時候,有時候需要將多個文件打包成一個文件後提供下載。
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.zip.Zip64Mode;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* 壓縮文件下載工具類。用於將多文件文件壓縮後,提供給 ServletOutputStream 輸出流共用戶網頁下載
*/
@SuppressWarnings("all")
public class ZipFileDownloadUtils {
/**
* 壓縮本地多個文件並提供輸出流下載
*
* @param filePaths :本地文件路徑,如 ["C:\\wmx\\temp\\data1.json","C:\\wmx\\temp\\data2.json"]。只支持文件,不能有目錄,否則拋異常。
* @param zipFileName :壓縮文件輸出的名稱,如 "年終總結" 不含擴展名。默認文件當前時間。如 20200108111213.zip
* @param response :提供輸出流
*/
public static void zipFileDownloadByFile(Set<String> filePaths, String zipFileName, HttpServletResponse response) {
try {
//1)參數校驗
if (filePaths == null || filePaths.size() <= 0) {
throw new RuntimeException("待壓縮導出文件爲空.");
}
if (zipFileName == null || "".equals(zipFileName)) {
zipFileName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".zip";
} else {
zipFileName = zipFileName + ".zip";
}
//2)設置 response 參數。這裏文件名如果是中文,則導出亂碼,可以
response.reset();
response.setContentType("content-type:octet-stream;charset=UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(zipFileName, "utf-8"));
//3)通過 OutputStream 創建 zip 壓縮流。如果是壓縮到本地,也可以直接使用 ZipArchiveOutputStream(final File file)
ServletOutputStream servletOutputStream = response.getOutputStream();
ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(servletOutputStream);
//4)setUseZip64(final Zip64Mode mode):是否使用 Zip64 擴展。
// Zip64Mode 枚舉有 3 個值:Always:對所有條目使用 Zip64 擴展、Never:不對任何條目使用Zip64擴展、AsNeeded:對需要的所有條目使用Zip64擴展
zipArchiveOutputStream.setUseZip64(Zip64Mode.AsNeeded);
for (String filePath : filePaths) {
File file = new File(filePath);
String fileName = file.getName();
InputStream inputStream = new FileInputStream(file);
//5)使用 ByteArrayOutputStream 讀取文件字節
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int readLength = -1;
while ((readLength = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, readLength);
}
if (byteArrayOutputStream != null) {
byteArrayOutputStream.flush();
}
byte[] fileBytes = byteArrayOutputStream.toByteArray();//整個文件字節數據
//6)用指定的名稱創建一個新的 zip 條目(zip壓縮實體),然後設置到 zip 壓縮輸出流中進行寫入.
ArchiveEntry entry = new ZipArchiveEntry(fileName);
zipArchiveOutputStream.putArchiveEntry(entry);
//6.1、write(byte b[]):從指定的字節數組寫入 b.length 個字節到此輸出流
zipArchiveOutputStream.write(fileBytes);
//6.2、寫入此條目的所有必要數據。如果條目未壓縮或壓縮後的大小超過4 GB 則拋出異常
zipArchiveOutputStream.closeArchiveEntry();
if (byteArrayOutputStream != null) {
byteArrayOutputStream.close();
}
}
//7)最後關閉 zip 壓縮輸出流.
if (zipArchiveOutputStream != null) {
zipArchiveOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 壓縮本地多個文件並提供輸出流下載。只支持文件,不能有目錄,否則拋異常。
*
* @param fileLists :本地文件,如 ["C:\\wmx\\temp\\data1.json","C:\\wmx\\temp\\data2.json"]。
* @param zipFileName :壓縮文件輸出的名稱,如 "年終總結" 不含擴展名。默認文件當前時間。如 20200108111213.zip
* @param response :提供輸出流
*/
public static void zipFileDownloadByFile(List<File> fileLists, String zipFileName, HttpServletResponse response) {
if (fileLists == null || fileLists.size() <= 0) {
throw new RuntimeException("待壓縮導出文件爲空.");
}
Set<String> filePaths = new HashSet<>();
for (File file : fileLists) {
filePaths.add(file.getAbsolutePath());
}
zipFileDownloadByFile(filePaths, zipFileName, response);
}
/**
* 壓縮網絡文件。
*
* @param urlLists,待壓縮的網絡文件地址,如 ["http://www.baidu.com/img/bd_logo1.png"]
* @param zipFileName
* @param response
*/
public static void zipFileDownloadByUrl(List<URL> urlLists, String zipFileName, HttpServletResponse response) {
try {
//1)參數校驗
if (urlLists == null || urlLists.size() <= 0) {
throw new RuntimeException("待壓縮導出文件爲空.");
}
if (zipFileName == null || "".equals(zipFileName)) {
zipFileName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".zip";
} else {
zipFileName = zipFileName + ".zip";
}
//2)設置 response 參數
response.reset();
response.setContentType("content-type:octet-stream;charset=UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(zipFileName, "utf-8"));
//3)通過 OutputStream 創建 zip 壓縮流。如果是壓縮到本地,也可以直接使用 ZipArchiveOutputStream(final File file)
ServletOutputStream servletOutputStream = response.getOutputStream();
ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(servletOutputStream);
//4)setUseZip64(final Zip64Mode mode):是否使用 Zip64 擴展。
// Zip64Mode 枚舉有 3 個值:Always:對所有條目使用 Zip64 擴展、Never:不對任何條目使用Zip64擴展、AsNeeded:對需要的所有條目使用Zip64擴展
zipArchiveOutputStream.setUseZip64(Zip64Mode.AsNeeded);
for (URL url : urlLists) {
String fileName = getNameByUrl(url);
InputStream inputStream = url.openStream();
//5)使用 ByteArrayOutputStream 讀取文件字節
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int readLength = -1;
while ((readLength = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, readLength);
}
if (byteArrayOutputStream != null) {
byteArrayOutputStream.flush();
}
byte[] fileBytes = byteArrayOutputStream.toByteArray();//整個文件字節數據
//6)用指定的名稱創建一個新的 zip 條目(zip壓縮實體),然後設置到 zip 壓縮輸出流中進行寫入.
ArchiveEntry entry = new ZipArchiveEntry(fileName);
zipArchiveOutputStream.putArchiveEntry(entry);
//6.1、write(byte b[]):從指定的字節數組寫入 b.length 個字節到此輸出流
zipArchiveOutputStream.write(fileBytes);
//6.2、寫入此條目的所有必要數據。如果條目未壓縮或壓縮後的大小超過4 GB 則拋出異常
zipArchiveOutputStream.closeArchiveEntry();
if (byteArrayOutputStream != null) {
byteArrayOutputStream.close();
}
}
//7)最後關閉 zip 壓縮輸出流.
if (zipArchiveOutputStream != null) {
zipArchiveOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 壓縮網絡文件
*
* @param urlPaths 待壓縮的網絡文件地址,如 ["http://www.baidu.com/img/bd_logo1.png"]
* @param zipFileName
* @param response
*/
public static void zipFileDownloadByUrl(Set<String> urlPaths, String zipFileName, HttpServletResponse response) {
if (urlPaths == null || urlPaths.size() <= 0) {
throw new RuntimeException("待壓縮導出文件爲空.");
}
List<URL> urlList = new ArrayList<>();
for (String urlPath : urlPaths) {
try {
urlList.add(new URL(urlPath));
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
zipFileDownloadByUrl(urlList, zipFileName, response);
}
/**
* 通過 url 獲取文件的名稱
*
* @param url,如 http://www.baidu.com/img/bd_logo1.png
* @return
*/
private static String getNameByUrl(URL url) {
String name = url.toString();
int lastIndexOf1 = name.lastIndexOf("/");
int lastIndexOf2 = name.lastIndexOf("\\");
if (lastIndexOf1 > 0) {
name = name.substring(lastIndexOf1 + 1, name.length());
} else if (lastIndexOf2 > 0) {
name = name.substring(lastIndexOf1 + 2, name.length());
}
return name;
}
}