Java 实现文件【夹】压缩
JDK自带的压缩功能
JDK提供了 ZipOutputStream 和 ZipInputStream 用于实现文件(夹)的压缩及解压缩功能。
压缩 将需要压缩的文件写入 ZipOutputStream 流
解压 将需要解压的文件用流 ZipInputStream 读取出来
文件压缩代码
public class FileZip {
/**
* 生成压缩文件
* @param dir 文件夹
* @param path 生成的压缩文件的路径
*/
public static void generateZip(File dir, String path) {
try(ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(path))){
File[] files = dir.listFiles();
for (File file : files) {
if(file.isFile()){
ZipEntry entry = new ZipEntry(file.getName());
zipOutputStream.putNextEntry(entry);
copy(file, zipOutputStream);
}
}
// 完成压缩
zipOutputStream.finish();
} catch (IOException e) {
}
}
/**
* 文件复制
* @param file 文件
* @param zipOutputStream 流
*/
private static void copy(File file, ZipOutputStream zipOutputStream) {
try(FileInputStream fis = new FileInputStream(file)){
byte[] buffer = new byte[fis.available()];
fis.read(buffer);
zipOutputStream.write(buffer);
} catch (FileNotFoundException e) {
} catch (IOException e) {
}
}
}
用到的方法
-
putNextEntry(ZipEntry e)
- 开始写一个新的压缩文件Entry, 并将流指向Entry数据的开始位置 closeEntry()
- 关闭当前压缩Entry, 并将流指向下一个Entry finish()
- 完成压缩输出流内容的写入,不会关闭流
从上面的API可以看出:
- putNextEntry 以及 closeEntry 都具备改变流的指向的功能,所以closeEntry在demo中并没有使用到;
- 最后一定要调用 finish 方法将压缩文件流写入到文件中
使用STORED压缩算法
ZipOutputStream 默认使用的是 DEFLATED 算法对文件进行压缩,会造成压缩时间过长,而且相对有些资源压缩率也比较低(比如:图片),所以使用 STORED 压缩算法会更加好一些,STORED 采用的是不压缩的方式,直接将文件直接写入压缩文件。
public class FileZip {
/**
* 生成压缩文件
* @param dir 待压缩的文件夹
* @param path 生成的压缩文件的路径
*/
public static void generateZip(File dir, String path) {
try(ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(path))){
zipOutputStream.setMethod(ZipOutputStream.STORED);
File[] files = dir.listFiles();
List<FileMessage> messages = toFileMessages(files);
for (FileMessage message : messages) {
ZipEntry entry = new ZipEntry(message.getName());
entry.setMethod(ZipEntry.STORED);
entry.setSize(message.getLength());
entry.setCrc(message.getCrc());
zipOutputStream.putNextEntry(entry);
zipOutputStream.write(message.getBuffer());
}
zipOutputStream.finish();
} catch (IOException e) {
}
}
/**
* 转换成文件信息实体
* @param files 文件数组
* @return
*/
private static List<FileMessage> toFileMessages(File[] files) {
List<FileMessage> messages = new ArrayList<FileMessage>(files.length);
for (File file : files) {
if(file.isFile()){
FileMessage message = toFileMessage(file);
if(null != message){
messages.add(message);
}
}
}
return messages;
}
/**
* 转换成文件信息实体
* @param file 文件
* @return
*/
private static FileMessage toFileMessage(File file) {
try(FileInputStream fis = new FileInputStream(file);
CheckedInputStream cis = new CheckedInputStream(fis, new CRC32());){
byte[] buffer = new byte[fis.available()];
cis.read(buffer);
return new FileMessage(file.getName(), file.length(), buffer, cis.getChecksum().getValue());
} catch (FileNotFoundException e) {
} catch (IOException e) {
}
return null;
}
static class FileMessage {
// 文件名称
private String name;
// 文件长度
private long length;
private long crc;
// 文件字节数组
private byte[] buffer;
public FileMessage(String name, long length, byte[] buffer, long crc){
this.name = name;
this.length = length;
this.buffer = buffer;
this.crc = crc;
}
public FileMessage() {
}
public long getCrc() {
return crc;
}
public void setCrc(long crc){
this.crc = crc;
}
public byte[] getBuffer() {
return buffer;
}
public void setBuffer(byte[] buffer){
this.buffer = buffer;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getLength() {
return length;
}
public void setLength(long length){
this.length = length;
}
}
}
Apache 提供的API
引入
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.18</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
代码
public class Scatter {
private String path;
private File root;
private Integer method = ZipEntry.DEFLATED;
private ParallelScatterZipCreator scatterZipCreator = new ParallelScatterZipCreator();
private ScatterZipOutputStream dirs = ScatterZipOutputStream
.fileBased(File.createTempFile("whatever-preffix", ".whatever"));
public Scatter(String path) throws IOException {
this(path, ZipEntry.DEFLATED);
}
public Scatter(String path, Integer method) throws IOException {
this.path = path;
root = new File(path);
this.method = method;
}
public void generate(OutputStream os) {
try (final ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(os)) {
compressCurrentDirectory(root);
writeTo(zipArchiveOutputStream);
} catch (IOException | ExecutionException | InterruptedException e) {
}
}
public void generate(String targetPath) {
try {
generate(new FileOutputStream(targetPath));
} catch (FileNotFoundException e) {
}
}
public byte[] generate() {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
generate(buffer);
return buffer.toByteArray();
}
/**
* 压缩当前文件夹
*
* @param dir
* 文件夹
* @throws IOException
*/
private void compressCurrentDirectory(File dir) throws IOException {
// 如果是文件, 直接将文件写入
if (dir.isFile()) {
addEntry(dir.getName(), dir);
return;
}
// 获取所有的文件及文件夹
File[] files = dir.listFiles();
if (files.length == 0) { // 文件夹为空
String relativePath = getRelativePath(dir.getAbsolutePath()); // 获取相对路径
addEntry(relativePath, dir);
return;
}
// 遍历文件及文件夹
for (File f : dir.listFiles()) {
// 如果是文件夹,则遍历压缩文件夹
if (f.isDirectory()) {
compressCurrentDirectory(f);
} else {
// 获取相对路径
String relativePath = getRelativePath(f.getParent());
addEntry(relativePath + f.getName(), f);
}
}
}
/**
* 获取相对路径
*
* @param absolutePath
* 绝对路径
* @return
*/
private String getRelativePath(String absolutePath) {
return absolutePath.replace(path, "");
}
/**
* 添加实体
*
* @param entryName
* 实体名称
* @param currentFile
* 当前文件
* @throws IOException
*/
private void addEntry(String entryName, File file) throws IOException {
ZipArchiveEntry archiveEntry = new ZipArchiveEntry(entryName);
archiveEntry.setMethod(method);
final InputStreamSupplier supplier = new DefaultInputStreamSupplier(file);
if (archiveEntry.isDirectory() && !archiveEntry.isUnixSymlink()) {
dirs.addArchiveEntry(ZipArchiveEntryRequest.createZipArchiveEntryRequest(archiveEntry, supplier));
} else {
scatterZipCreator.addArchiveEntry(archiveEntry, supplier);
}
}
/**
* 写出
*
* @param zipArchiveOutputStream
* 压缩文件输出流
* @throws IOException
* @throws ExecutionException
* @throws InterruptedException
*/
private void writeTo(final ZipArchiveOutputStream zipArchiveOutputStream)
throws IOException, ExecutionException, InterruptedException {
dirs.writeTo(zipArchiveOutputStream);
dirs.close();
scatterZipCreator.writeTo(zipArchiveOutputStream);
}
static class DefaultInputStreamSupplier implements InputStreamSupplier {
private File file;
public DefaultInputStreamSupplier(File file) {
this.file = file;
}
@Override
public InputStream get() {
try {
return file.isDirectory() ? new NullInputStream(0) : new FileInputStream(file);
} catch (FileNotFoundException e) {
}
return new NullInputStream(0);
}
}
}
总结
JDK提供的压缩方式,没有提供多线程压缩的方案,使用多线程压缩需要自己来完成;而Apache提供的API支持多线程压缩,在压缩速度上使用Apache提供的API进行压缩要比JDK提供的方式进行压缩要快很多,建议使用Apache提供的压缩方式进行文件压缩。
参考
- https://blog.csdn.net/ahau10/article/details/52550430/