package com.tool.util;
import androidx.annotation.Size;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
* 压缩工具类。
* zip是将文件打包为zip格式的压缩文件。
* gzip是将文件打包为tar.gz格式的压缩文件。
* gzip只能对一个文件进行压缩,如果想压缩一大堆文件,就需要使用tar进行打包了。
*/
public class CompressUtil {
private static final byte[] ZIP_HEADER_1 = new byte[]{0x1F, (byte) 0x8B, 0x03, 0x04};
private static final byte[] ZIP_HEADER_2 = new byte[]{0x1F, (byte) 0x8B, 0x05, 0x06};
private static final int BUFF_SIZE = 1024;
/**
* 判断文件是否压缩
*/
public static boolean isCompressed(@Size(4) byte[] data) {
if (data == null || data.length < ZIP_HEADER_1.length) return false;
byte[] header = Arrays.copyOf(data, ZIP_HEADER_1.length);
return Arrays.equals(header, ZIP_HEADER_1) || Arrays.equals(header, ZIP_HEADER_2);
}
public static byte[] zip(byte[] data) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(baos);
byte[] result = null;
try {
String name = FileUtil.genNameByDate();
zip.putNextEntry(new ZipEntry(name));
zip.write(data);
zip.closeEntry();
zip.flush();
result = baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
zip.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
public static byte[] unzip(byte[] data) {
byte[] buffer = new byte[BUFF_SIZE];
int len;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ByteArrayInputStream bais = new ByteArrayInputStream(data);
ZipInputStream unzip = new ZipInputStream(bais);
byte[] result = null;
try {
//这里的getNextEntry实际上是从byte数组里面读一段,如果没写这句,则后续解析失败
ZipEntry zipEntry = unzip.getNextEntry();
while ((len = unzip.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
result = baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
unzip.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
public static byte[] gzip(byte[] data) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzip = null;
byte[] result = null;
try {
gzip = new GZIPOutputStream(baos);
gzip.write(data);
gzip.flush();
//gzip这里有点问题,必须close之后再取数据,否则有遗漏
gzip.close();
gzip = null;
result = baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (gzip != null) gzip.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
public static byte[] ungzip(byte[] data) {
byte[] buffer = new byte[BUFF_SIZE];
int len;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ByteArrayInputStream bais = new ByteArrayInputStream(data);
byte[] result = null;
GZIPInputStream ungzip = null;
try {
ungzip = new GZIPInputStream(bais);
while ((len = ungzip.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
result = baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (ungzip != null) ungzip.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
/**
* ZIP压缩
*
* @param file 待压缩的文件或文件夹
* @param zos 压缩流
* @param baseDir 相对压缩文件的相对路径
*/
private static void zip(File file, ZipOutputStream zos, String baseDir) {
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
zip(f, zos, baseDir + file.getName() + File.separator);
}
} else if (file.isFile()) {
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
ZipEntry zipEntry = new ZipEntry(baseDir + file.getName());
zos.putNextEntry(zipEntry);
int len;
byte[] buffer = new byte[BUFF_SIZE];
while ((len = fis.read(buffer)) != -1) {
zos.write(buffer, 0, len);
}
zos.closeEntry();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fis != null) fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* Zip压缩
*
* @param srcFile 待压缩的文件或文件夹
* @param dstDir 压缩至该目录,保持原文件名,后缀改为zip
*/
public static void zip(File srcFile, String dstDir) {
File file = new File(dstDir);
//需要判断该文件存在,且是文件夹
if (!file.exists() || !file.isDirectory()) file.mkdirs();
String dstPath = dstDir + File.separator + srcFile.getName() + ".zip";
FileOutputStream fos = null;
CheckedOutputStream cos = null;
ZipOutputStream zos = null;
try {
fos = new FileOutputStream(dstPath);
cos = new CheckedOutputStream(fos, new CRC32());
zos = new ZipOutputStream(cos);
zip(srcFile, zos, "");
zos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//关闭数据流的时候要先关闭外层,否则会报Stream Closed的错误
if (zos != null) zos.close();
if (cos != null) cos.close();
if (fos != null) fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 解压
*
* @param srcFile 待解压的文件
* @param dstDir 解压到该目录,生成一个与原文件名相同的文件或文件夹
*/
public static void unzip(File srcFile, String dstDir) {
File file = new File(dstDir);
//需要判断该文件存在,且是文件夹
if (!file.exists() || !file.isDirectory()) file.mkdirs();
ZipFile zipFile = null;
FileOutputStream fos = null;
InputStream is = null;
try {
//默认编码方式为UTF8
zipFile = new ZipFile(srcFile);
Enumeration extends ZipEntry> zipEntrys = zipFile.entries();
byte[] buffer = new byte[BUFF_SIZE];
int len = 0;
while (zipEntrys.hasMoreElements()) {
ZipEntry zipEntry = zipEntrys.nextElement();
String fileName = dstDir + File.separator + zipEntry.getName();
File tmpFile = new File(fileName);
File parent = tmpFile.getParentFile();
if (!parent.exists()) parent.mkdirs();
if (zipEntry.isDirectory()) {
if (!tmpFile.exists()) tmpFile.mkdirs();
} else {
fos = new FileOutputStream(tmpFile);
is = zipFile.getInputStream(zipEntry);
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
is.close();
is = null;
fos.flush();
fos.close();
fos = null;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (zipFile != null) zipFile.close();
if (is != null) is.close();
if (fos != null) fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* tar打包,GZip压缩
*
* @param file 待压缩的文件或文件夹
* @param taos 压缩流
* @param baseDir 相对压缩文件的相对路径
*/
private static void tarGZip(File file, TarArchiveOutputStream taos, String baseDir) {
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
tarGZip(f, taos, baseDir + file.getName() + File.separator);
}
} else {
byte[] buffer = new byte[BUFF_SIZE];
int len = 0;
FileInputStream fis = null;
TarArchiveEntry tarArchiveEntry = null;
try {
fis = new FileInputStream(file);
tarArchiveEntry = new TarArchiveEntry(file.getName());
tarArchiveEntry.setSize(file.length());
taos.putArchiveEntry(tarArchiveEntry);
while ((len = fis.read(buffer)) != -1) {
taos.write(buffer, 0, len);
}
taos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fis != null) fis.close();
if (tarArchiveEntry != null) taos.closeArchiveEntry();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* tar打包,GZip压缩
*
* @param srcFile 待压缩的文件或文件夹
* @param dstDir 压缩至该目录,保持原文件名,后缀改为zip
*/
public static void tarGZip(File srcFile, String dstDir) {
File file = new File(dstDir);
//需要判断该文件存在,且是文件夹
if (!file.exists() || !file.isDirectory()) file.mkdirs();
//先打包成tar格式
String dstTarPath = dstDir + File.separator + srcFile.getName() + ".tar";
String dstPath = dstTarPath + ".gz";
FileOutputStream fos = null;
TarArchiveOutputStream taos = null;
try {
fos = new FileOutputStream(dstTarPath);
taos = new TarArchiveOutputStream(fos);
tarGZip(srcFile, taos, "");
taos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//关闭数据流的时候要先关闭外层,否则会报Stream Closed的错误
if (taos != null) taos.close();
if (fos != null) fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
File tarFile = new File(dstTarPath);
fos = null;
GZIPOutputStream gzip = null;
FileInputStream fis = null;
try {
//再压缩成gz格式
fos = new FileOutputStream(dstPath);
gzip = new GZIPOutputStream(fos);
fis = new FileInputStream(tarFile);
int len = 0;
byte[] buffer = new byte[BUFF_SIZE];
while ((len = fis.read(buffer)) != -1) {
gzip.write(buffer, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fis != null) fis.close();
//关闭数据流的时候要先关闭外层,否则会报Stream Closed的错误
if (gzip != null) gzip.close();
if (fos != null) fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//删除生成的tar临时文件
if (tarFile.exists()) tarFile.delete();
}
/**
* GZip解压,tar解包
*
* @param srcFile 待压缩的文件或文件夹
* @param dstDir 压缩至该目录,保持原文件名,后缀改为zip
*/
public static void untarGZip(File srcFile, String dstDir) {
File file = new File(dstDir);
//需要判断该文件存在,且是文件夹
if (!file.exists() || !file.isDirectory()) file.mkdirs();
byte[] buffer = new byte[BUFF_SIZE];
FileInputStream fis = null;
GzipCompressorInputStream gcis = null;
TarArchiveInputStream tais = null;
try {
fis = new FileInputStream(srcFile);
gcis = new GzipCompressorInputStream(fis);
tais = new TarArchiveInputStream(gcis);
TarArchiveEntry tarArchiveEntry;
int len = 0;
while ((tarArchiveEntry = tais.getNextTarEntry()) != null) {
File f = new File(dstDir + File.separator + tarArchiveEntry.getName());
if (tarArchiveEntry.isDirectory()) f.mkdirs();
else {
File parent = f.getParentFile();
if (!parent.exists()) parent.mkdirs();
FileOutputStream fos = new FileOutputStream(f);
while ((len = tais.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
fos.flush();
fos.close();
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fis != null) fis.close();
//关闭数据流的时候要先关闭外层,否则会报Stream Closed的错误
if(tais != null) tais.close();
if(gcis != null) gcis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
在module的build.gradle中添加如下依赖
dependencies {
……
//提供tar+gzip的打包方式(TarArchive和GZipCompress)
//http://commons.apache.org/proper/commons-compress/download_compress.cgi
implementation 'org.apache.commons:commons-compress:1.18'
}
JUnit 测试如下(注意:文件路径需要改成自己的)
@Test
public void compress() {
assertTrue(ZipOutputStream.class == getDelater());
byte[] data = randomBytes(65535);
byte[] zipData = CompressUtil.zip(data);
byte[] unzipData = CompressUtil.unzip(zipData);
assertArrayEquals(data, unzipData);
byte[] gzipData = CompressUtil.gzip(data);
byte[] ungzipData = CompressUtil.ungzip(gzipData);
assertArrayEquals(data, ungzipData);
String dir = "D:\\test";
String path = "D:\\test\\Test Folder";
String target = "D:\\test\\A\\B";
CompressUtil.zip(new File(path), dir);
CompressUtil.unzip(new File(path + ".zip"), target);
CompressUtil.tarGZip(new File(path), dir);
CompressUtil.untarGZip(new File(path + ".tar.gz"), target);
}