[Java] 封裝zip內文件處理的函數,演示修改zip內的txt追加文本


作者: 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

參考文獻


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM