diff --git a/CHANGELOG.md b/CHANGELOG.md
index dbfc6203c..3b805ae2a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@
### 🐣新特性
* 【core 】 MapProxy支持return this的setter方法(pr#392@Gitee)
* 【core 】 BeeDSFactory移除sqlite事务修复代码,新版本BeeCP已修复
+* 【core 】 增加compress包,扩充Zip操作灵活性
### 🐞Bug修复
diff --git a/hutool-core/src/main/java/cn/hutool/core/compress/Deflate.java b/hutool-core/src/main/java/cn/hutool/core/compress/Deflate.java
new file mode 100755
index 000000000..f579a17da
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/compress/Deflate.java
@@ -0,0 +1,98 @@
+package cn.hutool.core.compress;
+
+import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.core.io.IoUtil;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterOutputStream;
+
+/**
+ * Deflate算法
+ * Deflate是同时使用了LZ77算法与哈夫曼编码(Huffman Coding)的一个无损数据压缩算法。
+ *
+ * @author looly
+ * @since 5.7.8
+ */
+public class Deflate implements Closeable {
+
+ private final InputStream source;
+ private OutputStream target;
+ private final boolean nowrap;
+
+ /**
+ * 创建Deflate
+ *
+ * @param source 源流
+ * @param target 目标流
+ * @param nowrap {@code true}表示兼容Gzip压缩
+ */
+ public static Deflate of(InputStream source, OutputStream target, boolean nowrap) {
+ return new Deflate(source, target, nowrap);
+ }
+
+ /**
+ * 构造
+ *
+ * @param source 源流
+ * @param target 目标流
+ * @param nowrap {@code true}表示兼容Gzip压缩
+ */
+ public Deflate(InputStream source, OutputStream target, boolean nowrap) {
+ this.source = source;
+ this.target = target;
+ this.nowrap = nowrap;
+ }
+
+ /**
+ * 获取目标流
+ *
+ * @return 目标流
+ */
+ public OutputStream getTarget() {
+ return this.target;
+ }
+
+ /**
+ * 将普通数据流压缩
+ *
+ * @param level 压缩级别,0~9
+ */
+ public Deflate deflater(int level) {
+ target= (target instanceof DeflaterOutputStream) ?
+ (DeflaterOutputStream) target : new DeflaterOutputStream(target, new Deflater(level, nowrap));
+ IoUtil.copy(source, target);
+ try {
+ ((DeflaterOutputStream)target).finish();
+ } catch (IOException e) {
+ throw new IORuntimeException(e);
+ }
+ return this;
+ }
+
+ /**
+ * 将压缩流解压到target中
+ */
+ public Deflate inflater() {
+ target = (target instanceof InflaterOutputStream) ?
+ (InflaterOutputStream) target : new InflaterOutputStream(target, new Inflater(nowrap));
+ IoUtil.copy(source, target);
+ try {
+ ((InflaterOutputStream)target).finish();
+ } catch (IOException e) {
+ throw new IORuntimeException(e);
+ }
+ return this;
+ }
+
+ @Override
+ public void close() {
+ IoUtil.close(this.target);
+ IoUtil.close(this.source);
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/compress/Gzip.java b/hutool-core/src/main/java/cn/hutool/core/compress/Gzip.java
new file mode 100755
index 000000000..9041e408e
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/compress/Gzip.java
@@ -0,0 +1,89 @@
+package cn.hutool.core.compress;
+
+import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.core.io.IoUtil;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * GZIP是用于Unix系统的文件压缩
+ * gzip的基础是DEFLATE
+ *
+ * @author looly
+ * @since 5.7.8
+ */
+public class Gzip implements Closeable {
+
+ private InputStream source;
+ private OutputStream target;
+
+ /**
+ * 创建Gzip
+ *
+ * @param source 源流
+ * @param target 目标流
+ */
+ public static Gzip of(InputStream source, OutputStream target) {
+ return new Gzip(source, target);
+ }
+
+ /**
+ * 构造
+ *
+ * @param source 源流
+ * @param target 目标流
+ */
+ public Gzip(InputStream source, OutputStream target) {
+ this.source = source;
+ this.target = target;
+ }
+
+ /**
+ * 获取目标流
+ *
+ * @return 目标流
+ */
+ public OutputStream getTarget() {
+ return this.target;
+ }
+
+ /**
+ * 将普通数据流压缩
+ */
+ public Gzip gzip() {
+ try {
+ target = (target instanceof GZIPOutputStream) ?
+ (GZIPOutputStream) target : new GZIPOutputStream(target);
+ IoUtil.copy(source, target);
+ ((GZIPOutputStream)target).finish();
+ } catch (IOException e) {
+ throw new IORuntimeException(e);
+ }
+ return this;
+ }
+
+ /**
+ * 将压缩流解压到target中
+ */
+ public Gzip unGzip() {
+ try {
+ source = (source instanceof GZIPInputStream) ?
+ (GZIPInputStream) source : new GZIPInputStream(source);
+ IoUtil.copy(source, target);
+ } catch (IOException e) {
+ throw new IORuntimeException(e);
+ }
+ return this;
+ }
+
+ @Override
+ public void close() {
+ IoUtil.close(this.target);
+ IoUtil.close(this.source);
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/compress/ZipReader.java b/hutool-core/src/main/java/cn/hutool/core/compress/ZipReader.java
new file mode 100755
index 000000000..f12f4f194
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/compress/ZipReader.java
@@ -0,0 +1,189 @@
+package cn.hutool.core.compress;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.ZipUtil;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.util.Enumeration;
+import java.util.function.Consumer;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Zip文件或流读取器,一般用于Zip文件解压
+ *
+ * @author looly
+ * @since 5.7.8
+ */
+public class ZipReader implements Closeable {
+
+ private ZipFile zipFile;
+ private ZipInputStream in;
+
+ /**
+ * 创建{@link ZipReader}
+ *
+ * @param zipFile 生成的Zip文件
+ * @param charset 编码
+ * @return {@link ZipReader}
+ */
+ public static ZipReader of(File zipFile, Charset charset) {
+ return new ZipReader(zipFile, charset);
+ }
+
+ /**
+ * 创建{@link ZipReader}
+ *
+ * @param in Zip输入的流,一般为输入文件流
+ * @param charset 编码
+ * @return {@link ZipReader}
+ */
+ public static ZipReader of(InputStream in, Charset charset) {
+ return new ZipReader(in, charset);
+ }
+
+ /**
+ * 构造
+ *
+ * @param zipFile 读取的的Zip文件
+ */
+ public ZipReader(File zipFile, Charset charset) {
+ this.zipFile = ZipUtil.toZipFile(zipFile, charset);
+ }
+
+ /**
+ * 构造
+ *
+ * @param zipFile 读取的的Zip文件
+ */
+ public ZipReader(ZipFile zipFile) {
+ this.zipFile = zipFile;
+ }
+
+ /**
+ * 构造
+ *
+ * @param in 读取的的Zip文件流
+ */
+ public ZipReader(InputStream in, Charset charset) {
+ this.in = new ZipInputStream(in, charset);
+ }
+
+ /**
+ * 构造
+ *
+ * @param zin 读取的的Zip文件流
+ */
+ public ZipReader(ZipInputStream zin) {
+ this.in = zin;
+ }
+
+ /**
+ * 获取指定路径的文件流
+ *
+ * @param path 路径
+ * @return 文件流
+ */
+ public InputStream get(String path) {
+ if (null != this.zipFile) {
+ final ZipFile zipFile = this.zipFile;
+ final ZipEntry entry = zipFile.getEntry(path);
+ if (null != entry) {
+ return ZipUtil.getStream(zipFile, entry);
+ }
+ } else {
+ throw new UnsupportedOperationException("Zip stream mode not support get!");
+ }
+
+ return null;
+ }
+
+ /**
+ * 解压到指定目录中
+ *
+ * @param outFile 解压到的目录
+ * @return 解压的目录
+ * @throws IORuntimeException IO异常
+ */
+ public File readTo(File outFile) throws IORuntimeException {
+ read((zipEntry) -> {
+ // FileUtil.file会检查slip漏洞,漏洞说明见http://blog.nsfocus.net/zip-slip-2/
+ File outItemFile = FileUtil.file(outFile, zipEntry.getName());
+ if (zipEntry.isDirectory()) {
+ // 目录
+ //noinspection ResultOfMethodCallIgnored
+ outItemFile.mkdirs();
+ } else {
+ InputStream in;
+ if (null != this.zipFile) {
+ in = ZipUtil.getStream(this.zipFile, zipEntry);
+ } else {
+ in = this.in;
+ }
+ // 文件
+ FileUtil.writeFromStream(in, outItemFile, false);
+ }
+ });
+ return outFile;
+ }
+
+ /**
+ * 读取并处理Zip文件中的每一个{@link ZipEntry}
+ *
+ * @param consumer {@link ZipEntry}处理器
+ * @throws IORuntimeException IO异常
+ */
+ public ZipReader read(Consumer consumer) throws IORuntimeException {
+ if (null != this.zipFile) {
+ readFromZipFile(consumer);
+ } else {
+ readFromStream(consumer);
+ }
+ return this;
+ }
+
+ @Override
+ public void close() throws IORuntimeException {
+ if(null != this.zipFile){
+ IoUtil.close(this.zipFile);
+ } else {
+ IoUtil.close(this.in);
+ }
+ }
+
+ /**
+ * 读取并处理Zip文件中的每一个{@link ZipEntry}
+ *
+ * @param consumer {@link ZipEntry}处理器
+ */
+ private void readFromZipFile(Consumer consumer) {
+ final Enumeration extends ZipEntry> em = zipFile.entries();
+ while (em.hasMoreElements()) {
+ consumer.accept(em.nextElement());
+ }
+ }
+
+ /**
+ * 读取并处理Zip流中的每一个{@link ZipEntry}
+ *
+ * @param consumer {@link ZipEntry}处理器
+ * @throws IORuntimeException IO异常
+ */
+ private void readFromStream(Consumer consumer) throws IORuntimeException {
+ try {
+ ZipEntry zipEntry;
+ while (null != (zipEntry = in.getNextEntry())) {
+ consumer.accept(zipEntry);
+ }
+ } catch (IOException e) {
+ throw new IORuntimeException(e);
+ }
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/compress/ZipWriter.java b/hutool-core/src/main/java/cn/hutool/core/compress/ZipWriter.java
new file mode 100755
index 000000000..a9a86731f
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/compress/ZipWriter.java
@@ -0,0 +1,270 @@
+package cn.hutool.core.compress;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.io.resource.Resource;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Zip生成封装
+ *
+ * @author looly
+ * @since 5.7.8
+ */
+public class ZipWriter implements Closeable {
+
+ /**
+ * 创建{@link ZipWriter}
+ *
+ * @param zipFile 生成的Zip文件
+ * @param charset 编码
+ * @return {@link ZipWriter}
+ */
+ public static ZipWriter of(File zipFile, Charset charset) {
+ return new ZipWriter(zipFile, charset);
+ }
+
+ /**
+ * 创建{@link ZipWriter}
+ *
+ * @param out Zip输出的流,一般为输出文件流
+ * @param charset 编码
+ * @return {@link ZipWriter}
+ */
+ public static ZipWriter of(OutputStream out, Charset charset) {
+ return new ZipWriter(out, charset);
+ }
+
+ private final ZipOutputStream out;
+
+ /**
+ * 构造
+ *
+ * @param zipFile 生成的Zip文件
+ */
+ public ZipWriter(File zipFile, Charset charset) {
+ this.out = getZipOutputStream(zipFile, charset);
+ }
+
+ /**
+ * 构造
+ *
+ * @param out {@link ZipOutputStream}
+ */
+ public ZipWriter(OutputStream out, Charset charset) {
+ this.out = getZipOutputStream(out, charset);
+ }
+
+ /**
+ * 构造
+ *
+ * @param out {@link ZipOutputStream}
+ */
+ public ZipWriter(ZipOutputStream out) {
+ this.out = out;
+ }
+
+ /**
+ * 设置压缩级别,可选1~9,-1表示默认
+ *
+ * @param level 压缩级别
+ * @return this
+ */
+ public ZipWriter setLevel(int level) {
+ this.out.setLevel(level);
+ return this;
+ }
+
+ /**
+ * 设置注释
+ *
+ * @param comment 注释
+ * @return this
+ */
+ public ZipWriter setComment(String comment) {
+ this.out.setComment(comment);
+ return this;
+ }
+
+ /**
+ * 获取原始的{@link ZipOutputStream}
+ *
+ * @return {@link ZipOutputStream}
+ */
+ public ZipOutputStream getOut() {
+ return this.out;
+ }
+
+ /**
+ * 对文件或文件目录进行压缩
+ *
+ * @param withSrcDir 是否包含被打包目录,只针对压缩目录有效。若为false,则只压缩目录下的文件或目录,为true则将本目录也压缩
+ * @param filter 文件过滤器,通过实现此接口,自定义要过滤的文件(过滤掉哪些文件或文件夹不加入压缩),{@code null}表示不过滤
+ * @param files 要压缩的源文件或目录。如果压缩一个文件,则为该文件的全路径;如果压缩一个目录,则为该目录的顶层目录路径
+ * @throws IORuntimeException IO异常
+ * @since 5.1.1
+ */
+ public ZipWriter add(boolean withSrcDir, FileFilter filter, File... files) throws IORuntimeException {
+ for (File file : files) {
+ // 如果只是压缩一个文件,则需要截取该文件的父目录
+ String srcRootDir;
+ try {
+ srcRootDir = file.getCanonicalPath();
+ if ((false == file.isDirectory()) || withSrcDir) {
+ // 若是文件,则将父目录完整路径都截取掉;若设置包含目录,则将上级目录全部截取掉,保留本目录名
+ srcRootDir = file.getCanonicalFile().getParentFile().getCanonicalPath();
+ }
+ } catch (IOException e) {
+ throw new IORuntimeException(e);
+ }
+
+ _add(file, srcRootDir, filter);
+ }
+ return this;
+ }
+
+ /**
+ * 添加资源到压缩包,添加后关闭资源流
+ *
+ * @param resources 需要压缩的资源,资源的路径为{@link Resource#getName()}
+ * @throws IORuntimeException IO异常
+ */
+ public ZipWriter add(Resource... resources) throws IORuntimeException {
+ for (Resource resource : resources) {
+ if (null != resource) {
+ add(resource.getName(), resource.getStream());
+ }
+ }
+ return this;
+ }
+
+ /**
+ * 添加文件流到压缩包,添加后关闭输入文件流
+ * 如果输入流为{@code null},则只创建空目录
+ *
+ * @param path 压缩的路径, {@code null}和""表示根目录下
+ * @param in 需要压缩的输入流,使用完后自动关闭,{@code null}表示加入空目录
+ * @throws IORuntimeException IO异常
+ */
+ public ZipWriter add(String path, InputStream in) throws IORuntimeException {
+ path = StrUtil.nullToEmpty(path);
+ if (null == in) {
+ // 空目录需要检查路径规范性,目录以"/"结尾
+ path = StrUtil.addSuffixIfNot(path, StrUtil.SLASH);
+ if (StrUtil.isBlank(path)) {
+ return this;
+ }
+ }
+
+ return putEntry(path, in);
+ }
+
+ @Override
+ public void close() throws IORuntimeException {
+ try {
+ out.finish();
+ } catch (IOException e) {
+ throw new IORuntimeException(e);
+ } finally {
+ IoUtil.close(this.out);
+ }
+ }
+
+ /**
+ * 获得 {@link ZipOutputStream}
+ *
+ * @param zipFile 压缩文件
+ * @param charset 编码
+ * @return {@link ZipOutputStream}
+ */
+ private static ZipOutputStream getZipOutputStream(File zipFile, Charset charset) {
+ return getZipOutputStream(FileUtil.getOutputStream(zipFile), charset);
+ }
+
+ /**
+ * 获得 {@link ZipOutputStream}
+ *
+ * @param out 压缩文件流
+ * @param charset 编码
+ * @return {@link ZipOutputStream}
+ */
+ private static ZipOutputStream getZipOutputStream(OutputStream out, Charset charset) {
+ if (out instanceof ZipOutputStream) {
+ return (ZipOutputStream) out;
+ }
+ return new ZipOutputStream(out, charset);
+ }
+
+ /**
+ * 递归压缩文件夹或压缩文件
+ * srcRootDir决定了路径截取的位置,例如:
+ * file的路径为d:/a/b/c/d.txt,srcRootDir为d:/a/b,则压缩后的文件与目录为结构为c/d.txt
+ *
+ * @param srcRootDir 被压缩的文件夹根目录
+ * @param file 当前递归压缩的文件或目录对象
+ * @param filter 文件过滤器,通过实现此接口,自定义要过滤的文件(过滤掉哪些文件或文件夹不加入压缩),{@code null}表示不过滤
+ * @throws IORuntimeException IO异常
+ */
+ private ZipWriter _add(File file, String srcRootDir, FileFilter filter) throws IORuntimeException {
+ if (null == file || (null != filter && false == filter.accept(file))) {
+ return this;
+ }
+
+ // 获取文件相对于压缩文件夹根目录的子路径
+ final String subPath = FileUtil.subPath(srcRootDir, file);
+ if (file.isDirectory()) {
+ // 如果是目录,则压缩压缩目录中的文件或子目录
+ final File[] files = file.listFiles();
+ if (ArrayUtil.isEmpty(files)) {
+ // 加入目录,只有空目录时才加入目录,非空时会在创建文件时自动添加父级目录
+ add(subPath, null);
+ } else {
+ // 压缩目录下的子文件或目录
+ for (File childFile : files) {
+ _add(childFile, srcRootDir, filter);
+ }
+ }
+ } else {
+ // 如果是文件或其它符号,则直接压缩该文件
+ putEntry(subPath, FileUtil.getInputStream(file));
+ }
+ return this;
+ }
+
+ /**
+ * 添加文件流到压缩包,添加后关闭输入文件流
+ * 如果输入流为{@code null},则只创建空目录
+ *
+ * @param path 压缩的路径, {@code null}和""表示根目录下
+ * @param in 需要压缩的输入流,使用完后自动关闭,{@code null}表示加入空目录
+ * @throws IORuntimeException IO异常
+ */
+ private ZipWriter putEntry(String path, InputStream in) throws IORuntimeException {
+ try {
+ out.putNextEntry(new ZipEntry(path));
+ if (null != in) {
+ IoUtil.copy(in, out);
+ }
+ out.closeEntry();
+ } catch (IOException e) {
+ throw new IORuntimeException(e);
+ } finally {
+ IoUtil.close(in);
+ }
+
+ IoUtil.flush(this.out);
+ return this;
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/compress/package-info.java b/hutool-core/src/main/java/cn/hutool/core/compress/package-info.java
new file mode 100755
index 000000000..d26b8a178
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/compress/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * 压缩解压封装
+ *
+ * @author looly
+ * @since 5.7.8
+ */
+package cn.hutool.core.compress;
diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java
index d9851403f..cce4215a9 100644
--- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java
@@ -1,5 +1,9 @@
package cn.hutool.core.util;
+import cn.hutool.core.compress.Deflate;
+import cn.hutool.core.compress.Gzip;
+import cn.hutool.core.compress.ZipReader;
+import cn.hutool.core.compress.ZipWriter;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.core.io.FileUtil;
@@ -18,15 +22,8 @@ import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Enumeration;
import java.util.List;
import java.util.function.Consumer;
-import java.util.zip.Deflater;
-import java.util.zip.DeflaterOutputStream;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.GZIPOutputStream;
-import java.util.zip.Inflater;
-import java.util.zip.InflaterOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
@@ -35,6 +32,7 @@ import java.util.zip.ZipOutputStream;
/**
* 压缩工具类
*
+ * @see cn.hutool.core.compress.ZipWriter
* @author Looly
*/
public class ZipUtil {
@@ -210,13 +208,7 @@ public class ZipUtil {
*/
public static File zip(File zipFile, Charset charset, boolean withSrcDir, FileFilter filter, File... srcFiles) throws IORuntimeException {
validateFiles(zipFile, srcFiles);
-
- try (ZipOutputStream out = getZipOutputStream(zipFile, charset)) {
- zip(out, charset, withSrcDir, filter, srcFiles);
- } catch (IOException e) {
- throw new IORuntimeException(e);
- }
-
+ ZipWriter.of(zipFile, charset).add(withSrcDir, filter, srcFiles).close();
return zipFile;
}
@@ -232,7 +224,7 @@ public class ZipUtil {
* @since 5.1.1
*/
public static void zip(OutputStream out, Charset charset, boolean withSrcDir, FileFilter filter, File... srcFiles) throws IORuntimeException {
- zip(getZipOutputStream(out, charset), withSrcDir, filter, srcFiles);
+ ZipWriter.of(out, charset).add(withSrcDir, filter, srcFiles).close();
}
/**
@@ -244,27 +236,12 @@ public class ZipUtil {
* @param srcFiles 要压缩的源文件或目录。如果压缩一个文件,则为该文件的全路径;如果压缩一个目录,则为该目录的顶层目录路径
* @throws IORuntimeException IO异常
* @since 5.1.1
+ * @deprecated 请使用 {@link #zip(OutputStream, Charset, boolean, FileFilter, File...)}
*/
+ @Deprecated
public static void zip(ZipOutputStream zipOutputStream, boolean withSrcDir, FileFilter filter, File... srcFiles) throws IORuntimeException {
- String srcRootDir;
- try {
- for (File srcFile : srcFiles) {
- if (null == srcFile) {
- continue;
- }
- // 如果只是压缩一个文件,则需要截取该文件的父目录
- srcRootDir = srcFile.getCanonicalPath();
- if (srcFile.isFile() || withSrcDir) {
- // 若是文件,则将父目录完整路径都截取掉;若设置包含目录,则将上级目录全部截取掉,保留本目录名
- srcRootDir = srcFile.getCanonicalFile().getParentFile().getCanonicalPath();
- }
- // 调用递归压缩方法进行目录或文件压缩
- zip(srcFile, srcRootDir, zipOutputStream, filter);
- zipOutputStream.flush();
- }
- zipOutputStream.finish();
- } catch (IOException e) {
- throw new IORuntimeException(e);
+ try(final ZipWriter zipWriter = new ZipWriter(zipOutputStream)){
+ zipWriter.add(withSrcDir, filter, srcFiles);
}
}
@@ -355,13 +332,19 @@ public class ZipUtil {
* @since 3.0.9
*/
public static File zip(File zipFile, String[] paths, InputStream[] ins, Charset charset) throws UtilException {
- ZipOutputStream out = null;
- try {
- out = getZipOutputStream(zipFile, charset);
- zip(out, paths, ins);
- } finally {
- IoUtil.close(out);
+ if (ArrayUtil.isEmpty(paths) || ArrayUtil.isEmpty(ins)) {
+ throw new IllegalArgumentException("Paths or ins is empty !");
}
+ if (paths.length != ins.length) {
+ throw new IllegalArgumentException("Paths length is not equals to ins length !");
+ }
+
+ try(final ZipWriter zipWriter = ZipWriter.of(zipFile, charset)){
+ for (int i = 0; i < paths.length; i++) {
+ zipWriter.add(paths[i], ins[i]);
+ }
+ }
+
return zipFile;
}
@@ -374,12 +357,17 @@ public class ZipUtil {
* @since 5.5.2
*/
public static void zip(OutputStream out, String[] paths, InputStream[] ins) {
- ZipOutputStream zipOutputStream = null;
- try {
- zipOutputStream = getZipOutputStream(out, DEFAULT_CHARSET);
- zip(zipOutputStream, paths, ins);
- } finally {
- IoUtil.close(zipOutputStream);
+ if (ArrayUtil.isEmpty(paths) || ArrayUtil.isEmpty(ins)) {
+ throw new IllegalArgumentException("Paths or ins is empty !");
+ }
+ if (paths.length != ins.length) {
+ throw new IllegalArgumentException("Paths length is not equals to ins length !");
+ }
+
+ try(final ZipWriter zipWriter = ZipWriter.of(out, DEFAULT_CHARSET)){
+ for (int i = 0; i < paths.length; i++) {
+ zipWriter.add(paths[i], ins[i]);
+ }
}
}
@@ -399,8 +387,11 @@ public class ZipUtil {
if (paths.length != ins.length) {
throw new IllegalArgumentException("Paths length is not equals to ins length !");
}
- for (int i = 0; i < paths.length; i++) {
- add(ins[i], paths[i], zipOutputStream);
+
+ try(final ZipWriter zipWriter = new ZipWriter(zipOutputStream)){
+ for (int i = 0; i < paths.length; i++) {
+ zipWriter.add(paths[i], ins[i]);
+ }
}
}
@@ -416,15 +407,7 @@ public class ZipUtil {
* @since 5.5.2
*/
public static File zip(File zipFile, Charset charset, Resource... resources) throws UtilException {
- ZipOutputStream out = null;
- try {
- out = getZipOutputStream(zipFile, charset);
- for (Resource resource : resources) {
- add(resource.getStream(), resource.getName(), out);
- }
- } finally {
- IoUtil.close(out);
- }
+ ZipWriter.of(zipFile, charset).add(resources).close();
return zipFile;
}
@@ -541,21 +524,13 @@ public class ZipUtil {
*/
public static File unzip(ZipFile zipFile, File outFile) throws IORuntimeException {
if (outFile.exists() && outFile.isFile()) {
- throw new UtilException("Target path [{}] exist!", outFile.getAbsolutePath());
+ throw new IllegalArgumentException(
+ StrUtil.format("Target path [{}] exist!", outFile.getAbsolutePath()));
}
- read(zipFile, (zipEntry) -> {
- // FileUtil.file会检查slip漏洞,漏洞说明见http://blog.nsfocus.net/zip-slip-2/
- File outItemFile = FileUtil.file(outFile, zipEntry.getName());
- if (zipEntry.isDirectory()) {
- // 创建对应目录
- //noinspection ResultOfMethodCallIgnored
- outItemFile.mkdirs();
- } else {
- // 写出文件
- write(zipFile, zipEntry, outItemFile);
- }
- });
+ try(final ZipReader reader = new ZipReader(zipFile)){
+ reader.readTo(outFile);
+ }
return outFile;
}
@@ -596,13 +571,8 @@ public class ZipUtil {
* @since 5.5.2
*/
public static void read(ZipFile zipFile, Consumer consumer) {
- try {
- final Enumeration extends ZipEntry> em = zipFile.entries();
- while (em.hasMoreElements()) {
- consumer.accept(em.nextElement());
- }
- } finally {
- IoUtil.close(zipFile);
+ try(final ZipReader reader = new ZipReader(zipFile)){
+ reader.read(consumer);
}
}
@@ -635,18 +605,9 @@ public class ZipUtil {
* @since 4.5.8
*/
public static File unzip(ZipInputStream zipStream, File outFile) throws UtilException {
- read(zipStream, (zipEntry) -> {
- // FileUtil.file会检查slip漏洞,漏洞说明见http://blog.nsfocus.net/zip-slip-2/
- File outItemFile = FileUtil.file(outFile, zipEntry.getName());
- if (zipEntry.isDirectory()) {
- // 目录
- //noinspection ResultOfMethodCallIgnored
- outItemFile.mkdirs();
- } else {
- // 文件
- FileUtil.writeFromStream(zipStream, outItemFile, false);
- }
- });
+ try(final ZipReader reader = new ZipReader(zipStream)){
+ reader.readTo(outFile);
+ }
return outFile;
}
@@ -658,15 +619,8 @@ public class ZipUtil {
* @since 5.5.2
*/
public static void read(ZipInputStream zipStream, Consumer consumer) {
- try {
- ZipEntry zipEntry;
- while (null != (zipEntry = zipStream.getNextEntry())) {
- consumer.accept(zipEntry);
- }
- } catch (IOException e) {
- throw new UtilException(e);
- } finally {
- IoUtil.close(zipStream);
+ try(final ZipReader reader = new ZipReader(zipStream)){
+ reader.read(consumer);
}
}
@@ -716,23 +670,10 @@ public class ZipUtil {
* @return 文件内容bytes
* @since 4.1.8
*/
- @SuppressWarnings("unchecked")
public static byte[] unzipFileBytes(File zipFile, Charset charset, String name) {
- ZipFile zipFileObj = null;
- try {
- zipFileObj = toZipFile(zipFile, charset);
- final Enumeration em = (Enumeration) zipFileObj.entries();
- ZipEntry zipEntry;
- while (em.hasMoreElements()) {
- zipEntry = em.nextElement();
- if ((false == zipEntry.isDirectory()) && name.equals(zipEntry.getName())) {
- return IoUtil.readBytes(getStream(zipFileObj, zipEntry));
- }
- }
- } finally {
- IoUtil.close(zipFileObj);
+ try(final ZipReader reader = ZipReader.of(zipFile, charset)){
+ return IoUtil.readBytes(reader.get(name));
}
- return null;
}
// ----------------------------------------------------------------------------- Gzip
@@ -800,16 +741,7 @@ public class ZipUtil {
*/
public static byte[] gzip(InputStream in, int length) throws UtilException {
final ByteArrayOutputStream bos = new ByteArrayOutputStream(length);
- GZIPOutputStream gos = null;
- try {
- gos = new GZIPOutputStream(bos);
- IoUtil.copy(in, gos);
- } catch (IOException e) {
- throw new UtilException(e);
- } finally {
- IoUtil.close(gos);
- }
- // 返回必须在关闭gos后进行,因为关闭时会自动执行finish()方法,保证数据全部写出
+ Gzip.of(in, bos).gzip().close();
return bos.toByteArray();
}
@@ -857,18 +789,8 @@ public class ZipUtil {
* @since 4.1.18
*/
public static byte[] unGzip(InputStream in, int length) throws UtilException {
- GZIPInputStream gzi = null;
- FastByteArrayOutputStream bos;
- try {
- gzi = (in instanceof GZIPInputStream) ? (GZIPInputStream) in : new GZIPInputStream(in);
- bos = new FastByteArrayOutputStream(length);
- IoUtil.copy(gzi, bos);
- } catch (IOException e) {
- throw new UtilException(e);
- } finally {
- IoUtil.close(gzi);
- }
- // 返回必须在关闭gos后进行,因为关闭时会自动执行finish()方法,保证数据全部写出
+ FastByteArrayOutputStream bos = new FastByteArrayOutputStream(length);
+ Gzip.of(in, bos).unGzip().close();
return bos.toByteArray();
}
@@ -940,7 +862,7 @@ public class ZipUtil {
*/
public static byte[] zlib(InputStream in, int level, int length) {
final ByteArrayOutputStream out = new ByteArrayOutputStream(length);
- deflater(in, out, level, false);
+ Deflate.of(in, out, false).deflater(level);
return out.toByteArray();
}
@@ -988,7 +910,7 @@ public class ZipUtil {
*/
public static byte[] unZlib(InputStream in, int length) {
final ByteArrayOutputStream out = new ByteArrayOutputStream(length);
- inflater(in, out, false);
+ Deflate.of(in, out, false).inflater();
return out.toByteArray();
}
@@ -1023,123 +945,6 @@ public class ZipUtil {
// ---------------------------------------------------------------------------------------------- Private method start
- /**
- * 获得 {@link ZipOutputStream}
- *
- * @param zipFile 压缩文件
- * @param charset 编码
- * @return {@link ZipOutputStream}
- */
- private static ZipOutputStream getZipOutputStream(File zipFile, Charset charset) {
- return getZipOutputStream(FileUtil.getOutputStream(zipFile), charset);
- }
-
- /**
- * 获得 {@link ZipOutputStream}
- *
- * @param out 压缩文件流
- * @param charset 编码
- * @return {@link ZipOutputStream}
- */
- private static ZipOutputStream getZipOutputStream(OutputStream out, Charset charset) {
- if (out instanceof ZipOutputStream) {
- return (ZipOutputStream) out;
- }
- return new ZipOutputStream(out, ObjectUtil.defaultIfNull(charset, DEFAULT_CHARSET));
- }
-
- /**
- * 递归压缩文件夹
- * srcRootDir决定了路径截取的位置,例如:
- * file的路径为d:/a/b/c/d.txt,srcRootDir为d:/a/b,则压缩后的文件与目录为结构为c/d.txt
- *
- * @param out 压缩文件存储对象
- * @param srcRootDir 被压缩的文件夹根目录
- * @param file 当前递归压缩的文件或目录对象
- * @param filter 文件过滤器,通过实现此接口,自定义要过滤的文件(过滤掉哪些文件或文件夹不加入压缩)
- * @throws UtilException IO异常
- */
- private static void zip(File file, String srcRootDir, ZipOutputStream out, FileFilter filter) throws UtilException {
- if (null == file || (null != filter && false == filter.accept(file))) {
- return;
- }
-
- final String subPath = FileUtil.subPath(srcRootDir, file); // 获取文件相对于压缩文件夹根目录的子路径
- if (file.isDirectory()) {// 如果是目录,则压缩压缩目录中的文件或子目录
- final File[] files = file.listFiles();
- if (ArrayUtil.isEmpty(files)) {
- // 加入目录,只有空目录时才加入目录,非空时会在创建文件时自动添加父级目录
- addDir(subPath, out);
- } else{
- // 压缩目录下的子文件或目录
- for (File childFile : files) {
- zip(childFile, srcRootDir, out, filter);
- }
- }
- } else {// 如果是文件或其它符号,则直接压缩该文件
- add(file, subPath, out);
- }
- }
-
- /**
- * 添加文件到压缩包
- *
- * @param file 需要压缩的文件
- * @param path 在压缩文件中的路径
- * @param out 压缩文件存储对象
- * @throws IORuntimeException IO异常
- * @since 4.0.5
- */
- private static void add(File file, String path, ZipOutputStream out) throws IORuntimeException {
- add(FileUtil.getInputStream(file), path, out);
- }
-
- /**
- * 添加文件流到压缩包,添加后关闭流
- *
- * @param in 需要压缩的输入流,使用完后自动关闭
- * @param path 压缩的路径
- * @param out 压缩文件存储对象
- * @throws IORuntimeException IO异常
- */
- private static void add(InputStream in, String path, ZipOutputStream out) throws IORuntimeException {
- if (null == in) {
- return;
- }
- try {
- out.putNextEntry(new ZipEntry(path));
- IoUtil.copy(in, out);
- } catch (IOException e) {
- throw new IORuntimeException(e);
- } finally {
- IoUtil.close(in);
- closeEntry(out);
- }
- }
-
- /**
- * 在压缩包中新建目录
- *
- * @param path 压缩的路径
- * @param out 压缩文件存储对象
- * @throws UtilException IO异常
- */
- private static void addDir(String path, ZipOutputStream out) throws UtilException {
- if(StrUtil.isEmpty(path)){
- // 空路径不处理
- return;
- }
-
- path = StrUtil.addSuffixIfNot(path, StrUtil.SLASH);
- try {
- out.putNextEntry(new ZipEntry(path));
- } catch (IOException e) {
- throw new UtilException(e);
- } finally {
- closeEntry(out);
- }
- }
-
/**
* 判断压缩文件保存的路径是否为源文件路径的子文件夹,如果是,则抛出异常(防止无限递归压缩的发生)
*
@@ -1166,19 +971,6 @@ public class ZipUtil {
}
}
- /**
- * 关闭当前Entry,继续下一个Entry
- *
- * @param out ZipOutputStream
- */
- private static void closeEntry(ZipOutputStream out) {
- try {
- out.closeEntry();
- } catch (IOException e) {
- // ignore
- }
- }
-
/**
* 从Zip中读取文件流并写出到文件
*
@@ -1190,43 +982,6 @@ public class ZipUtil {
private static void write(ZipFile zipFile, ZipEntry zipEntry, File outItemFile) throws IORuntimeException {
FileUtil.writeFromStream(getStream(zipFile, zipEntry), outItemFile);
}
-
- /**
- * 将Zlib流解压到out中
- *
- * @param in zlib数据流
- * @param out 输出
- * @param nowrap true表示兼容Gzip压缩
- */
- @SuppressWarnings("SameParameterValue")
- private static void inflater(InputStream in, OutputStream out, boolean nowrap) {
- final InflaterOutputStream ios = (out instanceof InflaterOutputStream) ? (InflaterOutputStream) out : new InflaterOutputStream(out, new Inflater(nowrap));
- IoUtil.copy(in, ios);
- try {
- ios.finish();
- } catch (IOException e) {
- throw new IORuntimeException(e);
- }
- }
-
- /**
- * 将普通数据流压缩成zlib到out中
- *
- * @param in zlib数据流
- * @param out 输出
- * @param level 压缩级别,0~9
- * @param nowrap true表示兼容Gzip压缩
- */
- @SuppressWarnings("SameParameterValue")
- private static void deflater(InputStream in, OutputStream out, int level, boolean nowrap) {
- final DeflaterOutputStream ios = (out instanceof DeflaterOutputStream) ? (DeflaterOutputStream) out : new DeflaterOutputStream(out, new Deflater(level, nowrap));
- IoUtil.copy(in, ios);
- try {
- ios.finish();
- } catch (IOException e) {
- throw new IORuntimeException(e);
- }
- }
// ---------------------------------------------------------------------------------------------- Private method end
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/compress/ZipReaderTest.java b/hutool-core/src/test/java/cn/hutool/core/compress/ZipReaderTest.java
new file mode 100755
index 000000000..e1de7097c
--- /dev/null
+++ b/hutool-core/src/test/java/cn/hutool/core/compress/ZipReaderTest.java
@@ -0,0 +1,18 @@
+package cn.hutool.core.compress;
+
+import cn.hutool.core.lang.Console;
+import cn.hutool.core.util.ZipUtil;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.File;
+
+public class ZipReaderTest {
+
+ @Test
+ @Ignore
+ public void unzipTest() {
+ File unzip = ZipUtil.unzip("d:/java.zip", "d:/test/java");
+ Console.log(unzip);
+ }
+}
diff --git a/hutool-core/src/test/java/cn/hutool/core/compress/ZipWriterTest.java b/hutool-core/src/test/java/cn/hutool/core/compress/ZipWriterTest.java
new file mode 100755
index 000000000..97bb6af52
--- /dev/null
+++ b/hutool-core/src/test/java/cn/hutool/core/compress/ZipWriterTest.java
@@ -0,0 +1,16 @@
+package cn.hutool.core.compress;
+
+import cn.hutool.core.util.ZipUtil;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.File;
+
+public class ZipWriterTest {
+
+ @Test
+ @Ignore
+ public void zipDirTest() {
+ ZipUtil.zip(new File("d:/test"));
+ }
+}
diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java
index acc10d325..0246f6688 100644
--- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java
@@ -19,21 +19,21 @@ import java.nio.charset.Charset;
*
*/
public class ZipUtilTest {
-
-
+
+
@Test
@Ignore
public void zipDirTest() {
- ZipUtil.zip(new File("e:/picTest/picSubTest"));
+ ZipUtil.zip(new File("d:/test"));
}
-
+
@Test
@Ignore
public void unzipTest() {
File unzip = ZipUtil.unzip("f:/test/apache-maven-3.6.2.zip", "f:\\test");
Console.log(unzip);
}
-
+
@Test
@Ignore
public void unzipTest2() {
@@ -47,46 +47,46 @@ public class ZipUtilTest {
File unzip = ZipUtil.unzip(FileUtil.getInputStream("e:/test/hutool-core-5.1.0.jar"), FileUtil.file("e:/test/"), CharsetUtil.CHARSET_UTF_8);
Console.log(unzip);
}
-
+
@Test
@Ignore
public void unzipChineseTest() {
ZipUtil.unzip("d:/测试.zip");
}
-
+
@Test
@Ignore
public void unzipFileBytesTest() {
byte[] fileBytes = ZipUtil.unzipFileBytes(FileUtil.file("e:/02 电力相关设备及服务2-241-.zip"), CharsetUtil.CHARSET_GBK, "images/CE-EP-HY-MH01-ES-0001.jpg");
Assert.assertNotNull(fileBytes);
}
-
+
@Test
public void gzipTest() {
String data = "我是一个需要压缩的很长很长的字符串";
byte[] bytes = StrUtil.utf8Bytes(data);
byte[] gzip = ZipUtil.gzip(bytes);
-
+
//保证gzip长度正常
Assert.assertEquals(68, gzip.length);
-
+
byte[] unGzip = ZipUtil.unGzip(gzip);
//保证正常还原
Assert.assertEquals(data, StrUtil.utf8Str(unGzip));
}
-
+
@Test
public void zlibTest() {
String data = "我是一个需要压缩的很长很长的字符串";
byte[] bytes = StrUtil.utf8Bytes(data);
byte[] gzip = ZipUtil.zlib(bytes, 0);
-
+
//保证zlib长度正常
Assert.assertEquals(62, gzip.length);
byte[] unGzip = ZipUtil.unZlib(gzip);
//保证正常还原
Assert.assertEquals(data, StrUtil.utf8Str(unGzip));
-
+
gzip = ZipUtil.zlib(bytes, 9);
//保证zlib长度正常
Assert.assertEquals(56, gzip.length);