最近有个需求是要压缩指定目录下所有的文件及文价夹。看了网上一些实现,明白了大概流程,于是为了契合自己的业务需求,自己封装了个工具类。
支持功能
- 给定一个文件夹或文件的路径,输出的压缩包路径,生成压缩包。
- 给定一个路径集合,将不同路径的文件或文件夹下的包压缩在一个包下。
- 可以自行决定是否添加所有文件的上级文件夹名称。
实现代码
/**
* @Auther: binghua.zheng
* @Date: 2021/9/5 14:14
* @Description:
*/
public class FileCompressUtil {
private static final Logger log = LoggerFactory.getLogger(FileCompressUtil.class);
/**
* 文件夹层级连接符
*/
private static final String FOLDER_LEVEL = "/";
/**
* ""
*/
private static final String EMPTY_STR = "";
/**
* 压缩包根路径
*/
private static final ThreadLocal<String> PARENT_PATH = ThreadLocal.withInitial(() -> EMPTY_STR);
/**
* 压缩包根路径源
*/
private static final ThreadLocal<String> PARENT_SOURCE_PATH = ThreadLocal.withInitial(() -> EMPTY_STR);
/**
* 压缩包基础路径
*/
private static final ThreadLocal<String> BASE_PATH = ThreadLocal.withInitial(() -> EMPTY_STR);
/**
* 压缩文件
*
* @param fileUrl 准备压缩的文件或文件夹
* @param outZipUrl 要输出的压缩包路径
* @param retainFolder 如果是文件夹,是否要保留当前文件层级
*/
public static void compress(String fileUrl, String outZipUrl, boolean retainFolder) {
compress(fileUrl, outZipUrl, EMPTY_STR, retainFolder);
}
/**
* 压缩文件
*
* @param fileUrl 准备压缩的文件或文件夹
* @param outZipUrl 要输出的压缩包路径
* @param parentPath 要输出的压缩包父路径
* @param retainFolder 如果是文件夹,是否要保留当前文件层级
*/
public static void compress(String fileUrl, String outZipUrl, String parentPath, boolean retainFolder) {
try (
// 创建ZIP输出流
OutputStream os = new FileOutputStream(outZipUrl);
BufferedOutputStream bos = new BufferedOutputStream(os);
ZipOutputStream zos = new ZipOutputStream(bos)
){
File file = new File(fileUrl);
if (!checkFile(file)) {
log.error("找不到文件,无法压缩");
return ;
}
// 设定zip根文件夹
if (StringUtils.hasText(parentPath)) {
PARENT_PATH.set(parentPath + FOLDER_LEVEL);
}
// 如果有设置父路径,提前将父路径放入zip中c
doGenerateParentPath(zos);
// 以当前文件夹为起点来压缩
if (retainFolder && file.isDirectory()) {
createZipEntryBaseDirectory(file, zos);
}
// 配置基础路径
configBasePath(file);
// 开始压缩
compressFile(file, zos);
zos.closeEntry();
} catch (Exception e) {
e.printStackTrace();
log.error("****** 压缩文件失败 ******");
} finally {
// 清除缓存
PARENT_PATH.remove();
BASE_PATH.remove();
PARENT_SOURCE_PATH.remove();
}
}
/**
* 将指定路径的文件聚合在一起压缩
*
* @param filePaths 文件路径
* @param outZipUrl 要输出的压缩包路径
*/
public static void compress(List<String> filePaths, String outZipUrl) {
compress(filePaths, outZipUrl, EMPTY_STR);
}
/**
* 将指定路径的文件聚合在一起压缩
*
* @param filePaths 文件路径
* @param outZipUrl 要输出的压缩包路径
* @param parentPath 要输出的压缩包父路径
*/
public static void compress(List<String> filePaths, String outZipUrl, String parentPath) {
try (
// 创建ZIP输出流
OutputStream os = new FileOutputStream(outZipUrl);
BufferedOutputStream bos = new BufferedOutputStream(os);
ZipOutputStream zos = new ZipOutputStream(bos)
){
// 设定zip根文件夹
if (StringUtils.hasText(parentPath)) {
PARENT_PATH.set(parentPath + FOLDER_LEVEL);
PARENT_SOURCE_PATH.set(parentPath + FOLDER_LEVEL);
}
// 如果有设置父路径,提前将父路径放入zip中
doGenerateParentPath(zos);
// 循环创建压缩包
for (String filePath : filePaths) {
doProcessCompress(filePath, zos);
// 恢复原路径
PARENT_PATH.set(PARENT_SOURCE_PATH.get());
}
zos.closeEntry();
} catch (Exception e) {
e.printStackTrace();
log.error("****** 压缩文件失败 ******");
} finally {
// 清除缓存
PARENT_PATH.remove();
BASE_PATH.remove();
PARENT_SOURCE_PATH.remove();
}
}
/**
* 执行压缩流程
*
* @param fileUrl 文件路径
* @param zos ZIP输出流
*/
private static void doProcessCompress(String fileUrl, ZipOutputStream zos) throws IOException {
File file = new File(fileUrl);
if (!checkFile(file)) {
log.error("找不到文件,无法压缩");
return ;
}
// 基础路径
configBasePath(file);
// 如果是文件夹,则创建文件夹层级
createZipEntryBaseDirectory(file, zos);
// 开始压缩
compressFile(file, zos);
}
/**
* 校验文件是否存在
*
* @param file 文件
* @return true -> 存在; false -> 不存在
*/
private static boolean checkFile(File file) {
return file.exists();
}
/**
* 以文件夹为名,创建ZipEntry
*
* @param file 文件夹
* @param zos zip输出流
*/
private static void createZipEntryBaseDirectory(File file, ZipOutputStream zos) throws IOException {
if (file.isDirectory()) {
String zipPath = PARENT_PATH.get() + file.getName() + FOLDER_LEVEL;
doCreateZipEntry(zipPath, zos);
PARENT_PATH.set(zipPath);
}
}
/**
* 配置基础路径
*
* @param file 文件
*/
private static void configBasePath(File file) {
if (file.isFile()) {
BASE_PATH.set(file.getParent());
} else {
BASE_PATH.set(file.getPath());
}
}
/**
* 指定根文件,生成其内部所有文件的压缩包
*
* @param file 文件
* @param zos zip输出流
*/
private static void compressFile(File file, ZipOutputStream zos) throws IOException {
File[] fileList = file.isDirectory() ? file.listFiles() : new File[]{file};
if (fileList == null || fileList.length == 0) {
log.info("****** 找不到文件,过滤此文件压缩 ******");
return;
}
// 解析压缩所有子文件夹
for (File e : fileList) {
doCompressFile(e, zos);
}
}
/**
* 压缩文件
*
* @param file 文件
* @param zos zip输出流
*/
private static void doCompressFile(File file, ZipOutputStream zos) throws IOException {
// 以当前层级目录添加Zip文件入口
createZipEntry(file, zos);
// 写入输出流
if (file.isFile()) {
doWriteZipStream(file, zos);
} else {
// 继续解析子文件层级
compressFile(file, zos);
}
}
/**
* 根据File创建ZipEntry
*
* @param file 文件
* @param zos zip输出流
*/
private static void createZipEntry(File file, ZipOutputStream zos) throws IOException {
doCreateZipEntry(doGenerateZipEntryName(file), zos);
}
/**
* 根据文件或文件夹,生成压缩包结构路径
*
* @param file 文件
* @return 压缩包相对路径
*/
private static String doGenerateZipEntryName(File file) {
return PARENT_PATH.get() + file.getPath().substring(BASE_PATH.get().length() + 1) + (file.isDirectory() ? FOLDER_LEVEL : EMPTY_STR);
}
/**
* 设置父路径
*
* @param zos ZIP输出流
*/
private static void doGenerateParentPath(ZipOutputStream zos) throws IOException {
// 如果有设置父路径,提前将父路径放入zip中
if (StringUtils.hasText(PARENT_PATH.get())) {
doCreateZipEntry(PARENT_PATH.get(), zos);
}
}
/**
* 创建压缩包内的文件夹及文件层级
*
*
* @param pathName 相对压缩包路径的各文件路径
* @param zos zip输出流
*/
private static void doCreateZipEntry(String pathName, ZipOutputStream zos) throws IOException {
zos.putNextEntry(new ZipEntry(pathName));
}
/**
* 将文件写入ZIP输出流
*
* @param file 要压缩的文件
* @param zos ZIP输出流
*/
private static void doWriteZipStream(File file, ZipOutputStream zos) throws IOException {
InputStream is = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(is);
byte[] buf = new byte[1024];
int length;
while ((length = bis.read(buf)) > 0) {
zos.write(buf, 0, length);
}
bis.close();
is.close();
}
}
测试
指定目录
我要压缩文件夹1下所有的文件夹及文件。
@Test
public void test1() throws Exception {
String orderId = "123456";
String taskId = "123";
String reportId = "1";
String parentReportDate = "202109";
String reportDate = "20210905";
// 收集所有报告,准备打包
String url = REPORT_ROOT_PATH_PREFIX + LEVEL + parentReportDate + LEVEL + reportDate + LEVEL + orderId + CONNECT + taskId + LEVEL + reportId;
String outZipPath = url + ".zip";
// List<String> pathList = new ArrayList<>();
// pathList.add("G:\\防护报告\\202109\\20210905\\Spring Security 实战干货.pdf");
// pathList.add("G:\\防护报告\\202109\\20210905\\123456_123\\1\\pdf003");
// pathList.add("G:\\防护报告\\202109\\20210905\\123456_123\\1\\报告001");
// pathList.add("G:\\防护报告\\202109\\20210905\\123456_123\\1\\回执单");
// compress
FileCompressUtil.compress(url, outZipPath, "测试文件顶层目录", false);
}
解压后效果
指定多个路径
@Test
public void test1() throws Exception {
String orderId = "123456";
String taskId = "123";
String reportId = "1";
String parentReportDate = "202109";
String reportDate = "20210905";
// 收集所有报告,准备打包
String url = REPORT_ROOT_PATH_PREFIX + LEVEL + parentReportDate + LEVEL + reportDate + LEVEL + orderId + CONNECT + taskId + LEVEL + reportId;
String outZipPath = url + ".zip";
List<String> pathList = new ArrayList<>();
pathList.add("G:\\防护报告\\202109\\20210905\\Spring Security 实战干货.pdf");
pathList.add("G:\\防护报告\\202109\\20210905\\123456_123\\1\\pdf003");
pathList.add("G:\\防护报告\\202109\\20210905\\123456_123\\1\\报告001");
pathList.add("G:\\防护报告\\202109\\20210905\\123456_123\\1\\回执单");
// compress
FileCompressUtil.compress(pathList, outZipPath);
}
效果如下