作者: zyl910
一、緣由
上一篇文章演示了無需解壓的替換zip內檔案的技術原理,本文準備撰寫一個實際的例子——演示修改zip內的txt檔案,在后面追加文本,
二、封裝zip內檔案處理的函式
因為替換zip內檔案是一個比較常用的功能,于是考慮將zip壓縮流的處理封裝為一個函式,這就實作了解耦,使zip內的檔案資料處理不用再關心zip流的操作,只需關心自己的業務就行,
使用回呼函式方案來對業務進行解耦,于是制定了以下回呼函式:
byte[] transform(ZipEntry zipEntry, ZipInputStream zis);
對于zip中的每一個檔案,都會呼叫該回呼函式,回呼函式內可通過ZipEntry引數獲取當前檔案(zip專案)的資訊,可通過ZipInputStream引數來獲取當前檔案的資料,
回呼函式可通過回傳值來告知該檔案是否被修改,若回傳null,表示沿用原檔案的資料;若回傳非null的位元組陣列,則以回傳值為準,替換當前檔案的資料,
按照Java的傳統做法,對于回呼函式得建立新的介面,現在Java8提供了常用泛型函式式介面,這就不需要新建介面了,現在只需要2個輸入引數,故可選擇 BiFunction, 完整定義為 BiFunction<ZipEntry, ZipInputStream, byte[]> transform) .
完整代碼如下,
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import static java.util.zip.ZipEntry.STORED;
import static java.util.zip.ZipOutputStream.DEFLATED;
public class ZipStreamUtil {
public static byte[] toByteArray(InputStream in) throws IOException {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
copyStream(out, in);
return out.toByteArray();
}
}
public static void copyStream(OutputStream os, InputStream is) throws IOException {
copyStream(os, is, 0);
}
public static void copyStream(OutputStream os, InputStream is, int bufsize) throws IOException {
if (bufsize <= 0) bufsize = 4096;
int len;
byte[] bytes = new byte[bufsize];
while ((len = is.read(bytes)) != -1) {
os.write(bytes, 0, len);
}
}
/**
* 基于ZIP專案的復制Zip流.
*
* @param dst The output stream of the destination zip.
* @param src Source zip.
* @param transform 轉換處理. 可以為null, 不轉換. 該回呼函式的原型為`byte[] transform(ZipEntry zipEntry, ZipInputStream zis)`, 當回傳值為 null時保留原值, 為非null時用回傳值替換當前ZipEntry對應的流資料.
* @return 回傳轉換次數.
* @throws IOException
*/
public static int zipEntryCopyStreamZip(ZipOutputStream zos, ZipInputStream zis, BiFunction<ZipEntry, ZipInputStream, byte[]> transform) throws IOException {
int rt = 0;
ZipEntry se;
while ((se = zis.getNextEntry()) != null) {
if (null == se) continue;
//String line = String.format("ZipEntry(%s, isDirectory=%d, size=%d, compressedSize=%d, time=%d, crc=%d, method=%d, comment=%s)",
// se.getName(), (se.isDirectory())?1:0, se.getSize(), se.getCompressedSize(), se.getTime(), se.getCrc(), se.getMethod(), se.getComment());
//System.out.println(line);
byte[] dstBytes = null;
if (null != transform) {
dstBytes = transform.apply(se, zis);
}
// choose by dstBytes.
if (null == dstBytes) {
ZipEntry de = new ZipEntry(se);
de.setCompressedSize(-1); // 重新壓縮后, csize 可能不一致, 故需要恢復為默認值.
zos.putNextEntry(de);
copyStream(zos, zis);
zos.closeEntry();
} else {
++rt;
// == java.lang.IllegalArgumentException: invalid entry crc-32 at java.util.zip.ZipEntry.setCrc(ZipEntry.java:381)
//ZipEntry de = new ZipEntry(se);
//de.setCompressedSize(-1);
//de.setCrc(-1);
// == fix IllegalArgumentException.
ZipEntry de = new ZipEntry(se.getName());
//System.out.println(se.getTime());
//final long timeNone = 312739200000L;
//if (timeNone!=se.getTime() && null!=se.getLastModifiedTime()) { // 發現會被自動改為當前時間.
if (null != se.getLastModifiedTime()) {
de.setLastModifiedTime(se.getLastModifiedTime());
}
if (null != se.getLastAccessTime()) {
de.setLastAccessTime(se.getLastAccessTime());
}
if (null != se.getCreationTime()) {
de.setCreationTime(se.getCreationTime());
}
de.setSize(dstBytes.length);
//de.setCompressedSize(se.getCompressedSize()); // changed.
//de.setCrc(se.getCrc()); // changed.
int method = se.getMethod();
if (method != STORED && method != DEFLATED) {
// No setMethod .
} else {
de.setMethod(method);
}
de.setExtra(se.getExtra());
de.setComment(se.getComment());
zos.putNextEntry(de);
zos.write(dstBytes);
zos.closeEntry();
}
}
return rt;
}
/**
* 基于ZIP專案的復制流.
*
* @param dst The output stream of the destination zip.
* @param src Source zip.
* @param transform 轉換處理. 可以為null, 不轉換. 該回呼函式的原型為`byte[] transform(ZipEntry zipEntry, ZipInputStream zis)`, 當回傳值為 null時保留原值, 為非null時用回傳值替換當前ZipEntry對應的流資料.
* @return 回傳轉換次數.
* @throws IOException
*/
public static int zipEntryCopyStream(OutputStream os, InputStream is, BiFunction<ZipEntry, ZipInputStream, byte[]> transform) throws IOException {
try (ZipInputStream zis = new ZipInputStream(is)) {
try (ZipOutputStream zos = new ZipOutputStream(os)) {
return zipEntryCopyStreamZip(zos, zis, transform);
}
}
}
}
三、范例程式的原始碼
有了上面封裝好的 ZipStreamUtil.zipEntryCopyStream 后,便能很方便的做zip內檔案替換的功能了,
例如這個功能——修改zip內所有擴展名為txt的檔案,在后面追加一行文本,
范例程式的原始碼如下,
import java.io.*;
import java.util.Date;
import java.util.function.BiFunction;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class ZipTxtAppendTest {
public static void main(String[] args) {
String srcPath = "resources/text.zip";
String outPath = "E:\\test\\export\\text_append.zip";
try(FileInputStream is = new FileInputStream(srcPath)) {
try(FileOutputStream os = new FileOutputStream(outPath)) {
ZipStreamUtil.zipEntryCopyStream(os, is, new BiFunction<ZipEntry, ZipInputStream, byte[]>() {
@Override
public byte[] apply(ZipEntry se, ZipInputStream zis) {
byte[] rt = null;
String name = se.getName();
if (null==name) return rt;
String line = String.format("ZipEntry(%s, isDirectory=%d, size=%d, compressedSize=%d, time=%d, crc=%d, method=%d, comment=%s)",
se.getName(), (se.isDirectory())?1:0, se.getSize(), se.getCompressedSize(), se.getTime(), se.getCrc(), se.getMethod(), se.getComment());
System.out.println(line);
if (name.endsWith(".txt")) {
String appendString = String.format("\nZipTxtAppendTest %s\n", (new Date()).toString());
try {
//byte[] oldBytes = ZipStreamUtil.toByteArray(zis);
//String str = (new String(oldBytes)) + appendString;
//rt = str.getBytes();
// 為了避免多余的編碼轉換, 改成下面的代碼更好.
try(ByteArrayOutputStream buf = new ByteArrayOutputStream()) {
ZipStreamUtil.copyStream(buf, zis);
buf.write(appendString.getBytes());
rt = buf.toByteArray();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return rt;
}
});
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("ZipTxtAppendTest done." + outPath);
}
}
測驗結果如下,

原始碼地址:
https://github.com/zyl910/javademo/blob/master/io/zipstream/src/org/zyl910/javademo/io/zipstream/ZipTxtAppendTest.java
參考文獻
- Javadoc《Class ZipInputStream》. https://docs.oracle.com/javase/8/docs/api/index.html?java/util/zip/ZipInputStream.html
- Javadoc《Class ZipOutputStream》. https://docs.oracle.com/javase/8/docs/api/index.html?java/util/zip/ZipOutputStream.html
- zyl910《[Java] 遍歷zip內的資料,逐項復制流來生成新的zip檔案的范例(可用于替換zip內的檔案)》. https://www.cnblogs.com/zyl910/p/java_zip_zipcopytest.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/110725.html
標籤:Java
