zoukankan      html  css  js  c++  java
  • [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("
    ZipTxtAppendTest %s
    ", (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

    参考文献

  • 相关阅读:
    中国剩余定理
    hdu1808-Halloween treats(抽屉原理)
    快速幂算法
    因子和与因子个数
    乘性函数
    HDU 2669 Romantic (扩展欧几里得定理)
    扩展欧几里得算法
    Bi-shoe and Phi-shoe(欧拉函数)
    欧拉函数
    [51nod]1284 2 3 5 7的倍数(容斥原理)
  • 原文地址:https://www.cnblogs.com/zyl910/p/java_zip_ziptxtappendtest.html
Copyright © 2011-2022 走看看