平时我们都是使用WinZip,2345好压等软件来操作zip文件,java也提供了ZipOutputStream,ZipEntry等API创建和解析zip文件。
压缩
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class Client {
public static void main(String[] args) {
compressFileToZip("D:\\original_compute\\sku-20140802",
"D:\\original_compute\\sku-20140802.zip");
}
/**
* 读取文件内容并压缩,既支持文件也支持文件夹
*
* @param filePath 文件路径
*/
private static void compressFileToZip(String filePath,
String zipFilePath) {
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFilePath))) {
//递归的压缩文件夹和文件
doCompress("", filePath, zos);
//必须
zos.finish();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void doCompress(String parentFilePath, String filePath, ZipOutputStream zos) {
File sourceFile = new File(filePath);
if (!sourceFile.exists()) {
return;
}
String zipEntryName = parentFilePath + "/" + sourceFile.getName();
if (parentFilePath.isEmpty()) {
zipEntryName = sourceFile.getName();
}
if (sourceFile.isDirectory()) {
File[] childFiles = sourceFile.listFiles();
if (Objects.isNull(childFiles)) {
return;
}
for (File childFile : childFiles) {
doCompress(zipEntryName, childFile.getAbsolutePath(), zos);
}
} else {
int len = -1;
byte[] buf = new byte[1024];
try (InputStream input = new BufferedInputStream(new FileInputStream(sourceFile))) {
zos.putNextEntry(new ZipEntry(zipEntryName));
while ((len = input.read(buf)) != -1) {
zos.write(buf, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
每一个ZipEntry表示一个压缩子文件,如sku.html。注意,ZipEntry的name必须为目录名+文件名,如sku-20140802/sku/sku.html。
/**
* This class implements an output stream filter for writing files in the
* ZIP file format. Includes support for both compressed and uncompressed
* entries.
*
* @author David Connelly
* @since 1.1
*/
public
class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
/**
* 在写入文件内容之前写入一些zip格式相关的数据
*/
public void putNextEntry(ZipEntry e) throws IOException {
ensureOpen();
if (current != null) {
closeEntry(); // close previous entry
}
if (e.xdostime == -1) {
// by default, do NOT use extended timestamps in extra
// data, for now.
e.setTime(System.currentTimeMillis());
}
if (e.method == -1) {
e.method = method; // use default method
}
// store size, compressed size, and crc-32 in LOC header
e.flag = 0;
switch (e.method) {
case DEFLATED:
// store size, compressed size, and crc-32 in data descriptor
// immediately following the compressed entry data
if (e.size == -1 || e.csize == -1 || e.crc == -1)
e.flag = 8;
break;
case STORED:
// compressed size, uncompressed size, and crc-32 must all be
// set for entries using STORED compression method
if (e.size == -1) {
e.size = e.csize;
} else if (e.csize == -1) {
e.csize = e.size;
} else if (e.size != e.csize) {
throw new ZipException(
"STORED entry where compressed != uncompressed size");
}
if (e.size == -1 || e.crc == -1) {
throw new ZipException(
"STORED entry missing size, compressed size, or crc-32");
}
break;
default:
throw new ZipException("unsupported compression method");
}
if (! names.add(e.name)) {
throw new ZipException("duplicate entry: " + e.name);
}
if (zc.isUTF8())
e.flag |= USE_UTF8;
current = new XEntry(e, written);
xentries.add(current);
writeLOC(current);
}
/**
* 文件内容写入
*/
public synchronized void write(byte[] b, int off, int len)
throws IOException
{
ensureOpen();
if (off < 0 || len < 0 || off > b.length - len) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
if (current == null) {
throw new ZipException("no current ZIP entry");
}
ZipEntry entry = current.entry;
switch (entry.method) {
case DEFLATED:
super.write(b, off, len);
break;
case STORED:
written += len;
if (written - locoff > entry.size) {
throw new ZipException(
"attempt to write past end of STORED entry");
}
out.write(b, off, len);
break;
default:
throw new ZipException("invalid compression method");
}
crc.update(b, off, len);
}
/**
* 文件内容写入之后写入zip格式相关的数据
*/
public void finish() throws IOException {
ensureOpen();
if (finished) {
return;
}
if (current != null) {
closeEntry();
}
// write central directory
long off = written;
for (XEntry xentry : xentries)
writeCEN(xentry);
writeEND(off, written - off);
finished = true;
}
}
可以看到,ZipOutputStream也是扩展DeflaterOutputStream.
解压
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
public class Client {
public static void main(String[] args) throws Exception {
decompressFromZip("D:/original_compute/sku-20140802.zip",
"D:/original_compute/testzip/");
}
/**
* 文件解压缩,支持文件和文件夹的解压
*
* @param zipFilePath 压缩包路径
* @param destFilePath 解压路径
*/
private static void decompressFromZip(String zipFilePath, String destFilePath) {
File file = new File(zipFilePath);
try (ZipFile zipFile = new ZipFile(file);
ZipInputStream zis = new ZipInputStream(new FileInputStream(file))) {
ZipEntry zipEntry = null;
while ((zipEntry = zis.getNextEntry()) != null) {
String fileName = destFilePath + "/" + zipEntry.getName();
File entryFile = new File(fileName);
if (zipEntry.isDirectory()) {
//创建文件夹
entryFile.mkdir();
} else {
//创建文件之前必须保证父文件夹存在
if (!entryFile.getParentFile().exists()) {
entryFile.getParentFile().mkdirs();
}
//创建文件
entryFile.createNewFile();
}
try (InputStream input = zipFile.getInputStream(zipEntry);
OutputStream output = new FileOutputStream(entryFile)) {
int len = -1;
byte[] buf = new byte[1024];
while ((len = input.read(buf)) != -1) {
output.write(buf, 0, len);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
解压主要就是要获取到所有的压缩子文件,就是ZipEntry,将每一个ZipEntry重新生成文件或文件夹,生成文件时要确保父文件夹已经存在。