Java 如何實(shí)現(xiàn)解壓縮文件和文件夾
一 前言
項(xiàng)目開發(fā)中,總會遇到解壓縮文件的時候。比如,用戶下載多個文件時,服務(wù)端可以將多個文件壓縮成一個文件(例如xx.zip或xx.rar)。用戶上傳資料時,允許上傳壓縮文件,服務(wù)端進(jìn)行解壓讀取每一個文件。
基于通用性,以下介紹幾種解壓縮文件的方式,包裝成工具類,供平時開發(fā)使用。
二 壓縮文件
壓縮文件,顧名思義,即把一個或多個文件壓縮成一個文件。壓縮也有2種形式,一種是將所有文件壓縮到同一目錄下,此種方式要注意文件重名覆蓋的問題。另一種是按原有文件樹結(jié)構(gòu)進(jìn)行壓縮,即壓縮后的文件樹結(jié)構(gòu)保持不變。
壓縮文件操作,會使用到一個類,即ZipOutputStream。
2.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));
// 遍歷每一個文件,進(jìn)行輸出
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);
}
// 關(guān)閉流
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"));
}
2.2 壓縮文件或文件樹
此方法將文件夾下的所有文件按原有的樹形結(jié)構(gòu)壓縮到文件中,也支持壓縮單個文件。原理也簡單,無非就是遞歸遍歷文件樹中的每一個文件,進(jìn)行壓縮。有個注意的點(diǎn)每一個文件的寫入路徑是基于壓縮文件位置的相對路徑。
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) {
// 關(guān)閉流
try {
zipOutputStream.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
/**
* 遞歸壓縮文件
*
* @param file 當(dāng)前文件
* @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()) { // 當(dāng)前為文件夾
// 當(dāng)前文件夾下的所有文件
File[] list = file.listFiles();
if (null != list) {
// 計(jì)算當(dāng)前的相對路徑
relativePath += (relativePath.length() == 0 ? "" : "/") + file.getName();
// 遞歸壓縮每個文件
for (File f : list) {
zip(f, zipOutputStream, relativePath);
}
}
} else { // 壓縮文件
// 計(jì)算文件的相對路徑
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 {
// 關(guān)閉流
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文件中。
2.3 借助文件訪問器壓縮
還有一種更簡單的方式,我們不自己寫遞歸遍歷。借助Java原生類,SimpleFileVisitor,它提供了幾個訪問文件的方法,其中有個方法visitFile,對于文件樹中的每一個文件(文件夾除外),都會調(diào)用這個方法。我們只要寫一個類繼承SimpleFileVisitor,然后重寫visitFile方法,實(shí)現(xiàn)將每一個文件寫入到壓縮文件中即可。
當(dāng)然,除了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 {
// 關(guān)閉流
if (null != zipOutputStream) {
zipOutputStream.close();
}
}
}
// 遍歷到的每一個文件都會執(zhí)行此方法
@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();
// 繼續(xù)遍歷
return FileVisitResult.CONTINUE;
}
// 遍歷每一個目錄時都會調(diào)用的方法
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
return super.preVisitDirectory(dir, attrs);
}
// 遍歷完一個目錄下的所有文件后,再調(diào)用這個目錄的方法
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
return super.postVisitDirectory(dir, exc);
}
// 遍歷文件失敗后調(diào)用的方法
@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類,可以讀取到壓縮包中的每一個文件,然后根據(jù)讀取到的文件屬性,寫入到相應(yīng)路徑下即可。對于解壓壓縮包中是文件樹的結(jié)構(gòu),每讀取到一個文件后,如果是多層路徑下的文件,需要先創(chuàng)建父目錄,再寫入文件流。
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("創(chuàng)建解壓目標(biāo)文件夾失敗");
}
}
// 讀入流
ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipFilePath));
// 遍歷每一個文件
ZipEntry zipEntry = zipInputStream.getNextEntry();
while (zipEntry != null) {
if (zipEntry.isDirectory()) { // 文件夾
String unzipFilePath = desDirectory + File.separator + zipEntry.getName();
// 直接創(chuàng)建
mkdir(new File(unzipFilePath));
} else { // 文件
String unzipFilePath = desDirectory + File.separator + zipEntry.getName();
File file = new File(unzipFilePath);
// 創(chuàng)建父目錄
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();
}
// 如果父目錄不存在則創(chuàng)建
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);
}
}
四 總結(jié)
在解壓縮文件過程中,主要是對流的讀取操作,注意進(jìn)行異常處理,以及關(guān)閉流。
web應(yīng)用中,通過接口可以實(shí)現(xiàn)文件上傳下載,對應(yīng)的我們只要把壓縮后的文件,寫入到response.getOutputStream()輸出流即可。
解壓縮文件時,注意空文件夾的處理。
此演示項(xiàng)目已上傳到Github,如有需要可自行下載,歡迎 Star 。 https://github.com/LucioChn/common-utils
以上就是Java 如何實(shí)現(xiàn)解壓縮文件和文件夾的詳細(xì)內(nèi)容,更多關(guān)于Java 解壓縮文件和文件夾的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring注解實(shí)現(xiàn)Bean自動裝配示例詳解
這篇文章主要給大家介紹了關(guān)于Spring注解實(shí)現(xiàn)Bean自動裝配的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
SpringBoot整合WebService服務(wù)的實(shí)現(xiàn)代碼
WebService是一個SOA(面向服務(wù)的編程)的架構(gòu),它是不依賴于語言,不依賴于平臺,可以實(shí)現(xiàn)不同的語言間的相互調(diào)用,通過Internet進(jìn)行基于Http協(xié)議的網(wǎng)絡(luò)應(yīng)用間的交互,這篇文章主要介紹了SpringBoot整合WebService服務(wù)的實(shí)例代碼,需要的朋友可以參考下2022-02-02
Java之通過OutputStream寫入文件與文件復(fù)制問題
這篇文章主要介紹了Java之通過OutputStream寫入文件與文件復(fù)制問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04
MyBatis中動態(tài)SQL語句@Provider的用法
本文主要介紹了MyBatis中動態(tài)SQL語句@Provider的用法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
Java使用Maven BOM統(tǒng)一管理版本號的實(shí)現(xiàn)
這篇文章主要介紹了Java使用Maven BOM統(tǒng)一管理版本號的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04

