Android 将文件附加到 zip 文件而无需重写整个 zip 文件?

发布于 2024-11-02 14:52:58 字数 2337 浏览 1 评论 0原文

如何将文件附加到现有 zip 文件?我已经有了可以创建 zip 文件的代码,除了一个大问题之外,它工作得很好。现在的工作方式是,用户拍摄一堆照片,最后,所有照片都会添加到一个 zip 文件中,如果拍摄足够多的照片,这可能需要相当长的时间。 :-( 所以我在想,我有一个非常好的和有效的解决方案。在拍摄照片时,我会在拍摄后立即将每张新照片添加到 zip 文件中。然后当他们完成拍照时,完成 zip 文件,使其可用并导出:-)

问题是,我无法将文件添加到现有 zip 文件中。 :-( 这是我到目前为止所得到的。另外,请记住,这只是一个概念证明,我确实明白为 for 循环的每次迭代重新初始化所有内容是非常愚蠢的。循环的每次迭代都是应该代表正在添加的另一个文件,这很可能是很长一段时间后,甚至可能是一个小时后,这就是为什么我每次迭代都会重置所有内容,因为如果我能让这个工作正常,应用程序将在添加文件之间关闭。 ,那么我实际上会放弃 for 循环并将这段代码放入一个每次拍摄照片时都会调用的函数中:-)

        try  {
            for(int i=0; i < _files.size(); i++) {
                //beginning of initial setup stuff
                BufferedInputStream origin = null;
                FileOutputStream dest = new FileOutputStream(_zipFile,false); 
                ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest));
                byte data[] = new byte[BUFFER];
                out.setLevel(0); //I added this because it makes it not compress the data 
                //at all and I hoped that it would allow the zip to be appended to
                //end of initial setup stuff

                //beginning of old for loop
                Log.v("Compress", "Adding: " + _files[i]); 
                FileInputStream fi = new FileInputStream(_files[i]); 
                origin = new BufferedInputStream(fi, BUFFER); 
                ZipEntry entry = new ZipEntry(_files[i].substring(_files[i].lastIndexOf("/") + 1)); 
                out.putNextEntry(entry); 
                int count; 
                while ((count = origin.read(data, 0, BUFFER)) != -1) { 
                    out.write(data, 0, count); 
                } 
                origin.close();     
                //end of for old loop

                //beginning of finishing stuff
                out.close();
                //end of finishing stuff
            } 
        } catch(Exception e) {
            Log.e("ZipCreation", "Error writing zip", e);
            e.printStackTrace();
        }

另外,我已经尝试过,

FileOutputStream dest = new FileOutputStream(_zipFile,true);

如果您注意到,我将append设置为true,这实际上会将数据附加到现有文件中。有趣的是,它实际上确实将数据附加到原始文件中,但是,在我的计算机上提取文件后,最后写入的文件就是提取的所有文件,这很糟糕。 :-( 那么有没有什么方法可以开始编写 zip 文件,然后添加到它并完成 zip 文件?我什至考虑过可能采用 ZipOutputStream 并修改它以适合我需要的模型从逻辑上来说应该是可能的?:-)

提前感谢您的帮助! :-D-

贾里德

How can I append files to an existing zip file? I already have the code that can create a zip file and it works great except for one big problem. The way it works now, the user takes a bunch of pictures, and at the end, all the pictures get added to a zip file, which can take quite a while if you take enough pictures. :-( So I'm thinking, I have a very good and efficient solution. As the pictures are taken, I will simply add the each new picture to the zip file right after it's taken. Then when they're done taking pictures, finish up the zip file so it's usable and export it. :-)

The problem is, I can not get it to add files to an existing zip file. :-( Here's what I have so far. Also, please keep in mind, this is just a proof of concept, I do understand that re-initializing everything for every iteration of the for loop is very dumb. Each iteration of the loop is supposed to represent another file being added which will most likely be a long time later, maybe even an hour later, which is why I have everything resetting each iteration, because the app will be shut down between adding files. If I can get this working, then I will actually ditch the for loop and put this code into a function that gets called every time a picture gets taken. :-)

        try  {
            for(int i=0; i < _files.size(); i++) {
                //beginning of initial setup stuff
                BufferedInputStream origin = null;
                FileOutputStream dest = new FileOutputStream(_zipFile,false); 
                ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest));
                byte data[] = new byte[BUFFER];
                out.setLevel(0); //I added this because it makes it not compress the data 
                //at all and I hoped that it would allow the zip to be appended to
                //end of initial setup stuff

                //beginning of old for loop
                Log.v("Compress", "Adding: " + _files[i]); 
                FileInputStream fi = new FileInputStream(_files[i]); 
                origin = new BufferedInputStream(fi, BUFFER); 
                ZipEntry entry = new ZipEntry(_files[i].substring(_files[i].lastIndexOf("/") + 1)); 
                out.putNextEntry(entry); 
                int count; 
                while ((count = origin.read(data, 0, BUFFER)) != -1) { 
                    out.write(data, 0, count); 
                } 
                origin.close();     
                //end of for old loop

                //beginning of finishing stuff
                out.close();
                //end of finishing stuff
            } 
        } catch(Exception e) {
            Log.e("ZipCreation", "Error writing zip", e);
            e.printStackTrace();
        }

Also, I have experimented around with

FileOutputStream dest = new FileOutputStream(_zipFile,true);

If you notice, I set append to true, which will actually append the data to an existing file. And what's interesting is, it actually does append the data to the original file, however, after the file gets extracted on my computer, the last file written is all that gets extracted, which is bad. :-( So is there some way to start writing a zip file, and then later, add on to it, and finish up the zip file? I've even thought about possibly taking ZipOutputStream and modifying it to fit this model that I need. It should logically be possible somehow? :-)

Thanks in advance for the help! :-D

-Jared

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

雪化雨蝶 2024-11-09 14:52:58

好的,感谢您的所有建议,但我能够让它像我想要的那样工作......它可以完成,您可以在关闭文件后添加文件,只要您保存您的位置! :-D

以下是我如何让它正常工作:

        try  {
            for(int i=0; i < _files.size(); i++) {
                //beginning of initial setup stuff
                BufferedInputStream origin = null;
                FileOutputStream dest = new FileOutputStream(_zipFile,true); 
                ZipOutputStreamNew out = new ZipOutputStreamNew(new BufferedOutputStream(dest));
                byte data[] = new byte[BUFFER];
                if (havePreviousData) {
                    out.setWritten(tempWritten);
                    out.setXentries(tempXentries);
                }
                //end of initial setup stuff

                //beginning of for loop
                Log.i("Compress", "Adding: " + _files.get(i));
                FileInputStream fi = new FileInputStream(_files.get(i)); 
                origin = new BufferedInputStream(fi, BUFFER);
                TempString = _files.get(i).substring(_files.get(i).lastIndexOf("/") + 1);
                ZipEntry entry = new ZipEntry(_paths.get(i) + TempString);
                out.putNextEntry(entry);
                int count;
                while ((count = origin.read(data, 0, BUFFER)) != -1) { 
                    out.write(data, 0, count); 
                }
                origin.close();
                out.closeEntry();
                //end of for loop

                //beginning of finishing stuff
                if (i == (_files.size()-1)) {
                    //it's the last record so we should finish it off
                    out.closeAndFinish();
                } else {
                    //close the file, but don't write the Central Directory
                    //first, back up where the zip file was...
                    tempWritten = out.getWritten();
                    tempXentries = out.getXentries();
                    havePreviousData = true;
                    //now close the file
                    out.close();
                }
                //end of finishing stuff
            }
            //zip succeeded
    } catch(Exception e) {
        Log.e("ZipCreation", "Error writing zip", e);
        e.printStackTrace();
    }

另外,请记住,这不是我必须执行的唯一代码。我还必须制作自己的 ZipOutputStream 副本,以便可以公开我在 ZipOutputStreamNew 类中创建的以下函数......

getWritten()
getXentries()

以及

setWritten(long mWritten)
setXentries(Vector<XEntry> mXEntries)

在大多数情况下,所有这一切都会像正常一样开始编写,然后,它不会像平常一样关闭,而是备份这两个变量,然后在下一次迭代中,它只恢复这些变量。

如果您对这一切有任何疑问,请告诉我,但我知道它会起作用,它所要做的就是保存它所在的位置。 :-D

再次感谢大家的帮助! :-)

应 Raj 的要求,这里是 ZipOutputStreamNew 的源代码:

/**
 * 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
 * @version %I%, %G%
 */
public class ZipOutputStreamNew extends DeflaterOutputStream implements ZipConstants {
    public static class XEntry {
        public final ZipEntry entry;
        public final long offset;
        public final int flag;
        public XEntry(ZipEntry entry, long offset) {
            this.entry = entry;
            this.offset = offset;
            this.flag = (entry.getMethod() == DEFLATED &&
                 (entry.getSize() == -1 ||
                  entry.getCompressedSize() == -1 ||
                  entry.getCrc() == -1))
            // store size, compressed size, and crc-32 in data descriptor
            // immediately following the compressed entry data
            ? 8
            // store size, compressed size, and crc-32 in LOC header
            : 0;
        }
    }

    private XEntry current;
    private Vector<XEntry> xentries = new Vector<XEntry>();
    private HashSet<String> names = new HashSet<String>();
    private CRC32 crc = new CRC32();
    private long written = 0;
    private long locoff = 0;
    private String comment;
    private int method = DEFLATED;
    private boolean finished;

    private boolean closed = false;
    private boolean closeItPermanently = false;

    private static int version(ZipEntry e) throws ZipException {
    switch (e.getMethod()) {
    case DEFLATED: return 20;
    case STORED:   return 10;
    default: throw new ZipException("unsupported compression method");
    }
    }

    /**
     * Checks to make sure that this stream has not been closed.
     */
    private void ensureOpen() throws IOException {
    if (closed) {
        throw new IOException("Stream closed");
        }
    }
    /**
     * Compression method for uncompressed (STORED) entries.
     */
    public static final int STORED = ZipEntry.STORED;

    /**
     * Compression method for compressed (DEFLATED) entries.
     */
    public static final int DEFLATED = ZipEntry.DEFLATED;

    /**
     * Creates a new ZIP output stream.
     * @param out the actual output stream
     */
    public ZipOutputStreamNew(OutputStream out) {
    super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
        usesDefaultDeflater = true;
    }

    /**
     * Sets the ZIP file comment.
     * @param comment the comment string
     * @exception IllegalArgumentException if the length of the specified
     *        ZIP file comment is greater than 0xFFFF bytes
     */
    public void setComment(String comment) {
        if (comment != null && comment.length() > 0xffff/3
                                           && getUTF8Length(comment) > 0xffff) {
        throw new IllegalArgumentException("ZIP file comment too long.");
    }
    this.comment = comment;
    }

    /**
     * Sets the default compression method for subsequent entries. This
     * default will be used whenever the compression method is not specified
     * for an individual ZIP file entry, and is initially set to DEFLATED.
     * @param method the default compression method
     * @exception IllegalArgumentException if the specified compression method
     *        is invalid
     */
    public void setMethod(int method) {
    if (method != DEFLATED && method != STORED) {
        throw new IllegalArgumentException("invalid compression method");
    }
    this.method = method;
    }

    /**
     * Sets the compression level for subsequent entries which are DEFLATED.
     * The default setting is DEFAULT_COMPRESSION.
     * @param level the compression level (0-9)
     * @exception IllegalArgumentException if the compression level is invalid
     */
    public void setLevel(int level) {
    def.setLevel(level);
    }

    /**
     * Begins writing a new ZIP file entry and positions the stream to the
     * start of the entry data. Closes the current entry if still active.
     * The default compression method will be used if no compression method
     * was specified for the entry, and the current time will be used if
     * the entry has no set modification time.
     * @param e the ZIP entry to be written
     * @exception ZipException if a ZIP format error has occurred
     * @exception IOException if an I/O error has occurred
     */
    public void putNextEntry(ZipEntry e) throws IOException {
    ensureOpen();
    if (current != null) {
        closeEntry();   // close previous entry
    }
    if (e.getTime() == -1) {
        e.setTime(System.currentTimeMillis());
    }
    if (e.getMethod() == -1) {
        e.setMethod(method);    // use default method
    }
    switch (e.getMethod()) {
    case DEFLATED:
        break;
    case STORED:
        // compressed size, uncompressed size, and crc-32 must all be
        // set for entries using STORED compression method
        if (e.getSize() == -1) {
        e.setSize(e.getCompressedSize());
        } else if (e.getCompressedSize() == -1) {
        e.setCompressedSize(e.getSize());
        } else if (e.getSize() != e.getCompressedSize()) {
        throw new ZipException(
            "STORED entry where compressed != uncompressed size");
        }
        if (e.getSize() == -1 || e.getCrc() == -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.getName())) {
        throw new ZipException("duplicate entry: " + e.getName());
    }
    current = new XEntry(e, written);
    xentries.add(current);
        writeLOC(current);
    }

    /**
     * Closes the current ZIP entry and positions the stream for writing
     * the next entry.
     * @exception ZipException if a ZIP format error has occurred
     * @exception IOException if an I/O error has occurred
     */
    public void closeEntry() throws IOException {
    ensureOpen();
    if (current != null) {
        ZipEntry e = current.entry;
        switch (e.getMethod()) {
        case DEFLATED:
        def.finish();
        while (!def.finished()) {
            deflate();
        }
        if ((current.flag & 8) == 0) {
            // verify size, compressed size, and crc-32 settings
            if (e.getSize() != def.getBytesRead()) {
            throw new ZipException(
                "invalid entry size (expected " + e.getSize() +
                " but got " + def.getBytesRead() + " bytes)");
            }
            if (e.getCompressedSize() != def.getBytesWritten()) {
            throw new ZipException(
                "invalid entry compressed size (expected " +
                e.getCompressedSize() + " but got " + def.getBytesWritten() + " bytes)");
            }
            if (e.getCrc() != crc.getValue()) {
            throw new ZipException(
                "invalid entry CRC-32 (expected 0x" +
                Long.toHexString(e.getCrc()) + " but got 0x" +
                Long.toHexString(crc.getValue()) + ")");
            }
        } else {
            e.setSize(def.getBytesRead());
            e.setCompressedSize(def.getBytesWritten());
            e.setCrc(crc.getValue());
            writeEXT(e);
        }
        def.reset();
        written += e.getCompressedSize();
        break;
        case STORED:
        // we already know that both e.size and e.csize are the same
        if (e.getSize() != written - locoff) {
            throw new ZipException(
            "invalid entry size (expected " + e.getSize() +
            " but got " + (written - locoff) + " bytes)");
        }
        if (e.getCrc() != crc.getValue()) {
            throw new ZipException(
             "invalid entry crc-32 (expected 0x" +
             Long.toHexString(e.getCrc()) + " but got 0x" +
             Long.toHexString(crc.getValue()) + ")");
        }
        break;
        default:
        throw new ZipException("invalid compression method");
        }
        crc.reset();
        current = null;
    }
    }

    /**
     * Writes an array of bytes to the current ZIP entry data. This method
     * will block until all the bytes are written.
     * @param b the data to be written
     * @param off the start offset in the data
     * @param len the number of bytes that are written
     * @exception ZipException if a ZIP file error has occurred
     * @exception IOException if an I/O error has occurred
     */
    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.getMethod()) {
    case DEFLATED:
        super.write(b, off, len);
        break;
    case STORED:
        written += len;
        if (written - locoff > entry.getSize()) {
        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);
    }

    /**
     * Finishes writing the contents of the ZIP output stream without closing
     * the underlying stream. Use this method when applying multiple filters
     * in succession to the same output stream.
     * @exception ZipException if a ZIP file error has occurred
     * @exception IOException if an I/O exception has occurred
     */
    public void finish() throws IOException {
        ensureOpen();
        if (finished) {
            return;
        }
        if (current != null) {
            closeEntry();
        }
        if (xentries.size() < 1) {
            throw new ZipException("ZIP file must have at least one entry");
        }
        if (closeItPermanently) {
            // write central directory
            long off = written;
            for (XEntry xentry : xentries)
                writeCEN(xentry);
            writeEND(off, written - off);
            finished = true;
            //Log.e("ZipOutputStreamNew", "I just ran wrote the Central Directory Jared!");
        }
        //Log.e("ZipOutputStreamNew", "I just ran finish() Jared!");
    }

    /**
     * Gets the value of the "xentries" variable (for later use)
     * @return
     */
    public Vector<XEntry> getXentries() {
        return xentries;

        //TODO convert this to primitive data types
    }

    /**
     * Gets the value of the "written" variable (for later use)
     * @return
     */
    public long getWritten() {
        return written;
    }


    /**
     * Sets the value of the "xentries" variable (for later use)
     * @return
     */
    public void setXentries(Vector<XEntry> mXEntries) {
        xentries = mXEntries;
        //TODO convert this to primitive data types
    }

    /**
     * Sets the value of the "written" variable (for later use)
     * @return
     */
    public void setWritten(long mWritten) {
        written = mWritten;
    }


    /**
     * Closes the ZIP output stream as well as the stream being filtered.
     * @exception ZipException if a ZIP file error has occurred
     * @exception IOException if an I/O error has occurred
     */
    public void closeAndFinish() throws IOException {
        if (!closed) {
            closeItPermanently=true;
            super.close();
            closed = true;
        }
    }

    /**
     * Used to close the ZIP output stream as well as the stream being filtered.
     * instead it does nothing :-P
     * @exception ZipException if a ZIP file error has occurred
     * @exception IOException if an I/O error has occurred
     */
    public void close() throws IOException {
        if (!closed) {
            closeItPermanently=false;
            super.close();
            closed = true;
        }
    }

    /*
     * Writes local file (LOC) header for specified entry.
     */
    private void writeLOC(XEntry xentry) throws IOException {
    ZipEntry e = xentry.entry;
    int flag = xentry.flag;
    writeInt(LOCSIG);       // LOC header signature
    writeShort(version(e));     // version needed to extract
    writeShort(flag);           // general purpose bit flag
    writeShort(e.getMethod());       // compression method
    writeInt(e.getTime());           // last modification time
    if ((flag & 8) == 8) {
        // store size, uncompressed size, and crc-32 in data descriptor
        // immediately following compressed entry data
        writeInt(0);
        writeInt(0);
        writeInt(0);
    } else {
        writeInt(e.getCrc());        // crc-32
        writeInt(e.getCompressedSize());      // compressed size
        writeInt(e.getSize());       // uncompressed size
    }
    byte[] nameBytes = getUTF8Bytes(e.getName());
    writeShort(nameBytes.length);
    writeShort(e.getExtra() != null ? e.getExtra().length : 0);
    writeBytes(nameBytes, 0, nameBytes.length);
    if (e.getExtra() != null) {
        writeBytes(e.getExtra(), 0, e.getExtra().length);
    }
    locoff = written;
    }

    /*
     * Writes extra data descriptor (EXT) for specified entry.
     */
    private void writeEXT(ZipEntry e) throws IOException {
    writeInt(EXTSIG);       // EXT header signature
    writeInt(e.getCrc());       // crc-32
    writeInt(e.getCompressedSize());        // compressed size
    writeInt(e.getSize());      // uncompressed size
    }

    /*
     * Write central directory (CEN) header for specified entry.
     * REMIND: add support for file attributes
     */
    private void writeCEN(XEntry xentry) throws IOException {
    ZipEntry e  = xentry.entry;
    int flag = xentry.flag;
    int version = version(e);
    writeInt(CENSIG);       // CEN header signature
    writeShort(version);        // version made by
    writeShort(version);        // version needed to extract
    writeShort(flag);       // general purpose bit flag
    writeShort(e.getMethod());      // compression method
    writeInt(e.getTime());      // last modification time
    writeInt(e.getCrc());       // crc-32
    writeInt(e.getCompressedSize());        // compressed size
    writeInt(e.getSize());      // uncompressed size
    byte[] nameBytes = getUTF8Bytes(e.getName());
    writeShort(nameBytes.length);
    writeShort(e.getExtra() != null ? e.getExtra().length : 0);
    byte[] commentBytes;
    if (e.getComment() != null) {
        commentBytes = getUTF8Bytes(e.getComment());
        writeShort(commentBytes.length);
    } else {
        commentBytes = null;
        writeShort(0);
    }
    writeShort(0);          // starting disk number
    writeShort(0);          // internal file attributes (unused)
    writeInt(0);            // external file attributes (unused)
    writeInt(xentry.offset);    // relative offset of local header
    writeBytes(nameBytes, 0, nameBytes.length);
    if (e.getExtra() != null) {
        writeBytes(e.getExtra(), 0, e.getExtra().length);
    }
    if (commentBytes != null) {
        writeBytes(commentBytes, 0, commentBytes.length);
    }
    }

    /*
     * Writes end of central directory (END) header.
     */
    private void writeEND(long off, long len) throws IOException {
    int count = xentries.size();
    writeInt(ENDSIG);       // END record signature
    writeShort(0);          // number of this disk
    writeShort(0);          // central directory start disk
    writeShort(count);      // number of directory entries on disk
    writeShort(count);      // total number of directory entries
    writeInt(len);          // length of central directory
    writeInt(off);          // offset of central directory
    if (comment != null) {      // zip file comment
        byte[] b = getUTF8Bytes(comment);
        writeShort(b.length);
        writeBytes(b, 0, b.length);
    } else {
        writeShort(0);
    }
    }

    /*
     * Writes a 16-bit short to the output stream in little-endian byte order.
     */
    private void writeShort(int v) throws IOException {
    OutputStream out = this.out;
    out.write((v >>> 0) & 0xff);
    out.write((v >>> 8) & 0xff);
    written += 2;
    }

    /*
     * Writes a 32-bit int to the output stream in little-endian byte order.
     */
    private void writeInt(long v) throws IOException {
    OutputStream out = this.out;
    out.write((int)((v >>>  0) & 0xff));
    out.write((int)((v >>>  8) & 0xff));
    out.write((int)((v >>> 16) & 0xff));
    out.write((int)((v >>> 24) & 0xff));
    written += 4;
    }

    /*
     * Writes an array of bytes to the output stream.
     */
    private void writeBytes(byte[] b, int off, int len) throws IOException {
    super.out.write(b, off, len);
    written += len;
    }

    /*
     * Returns the length of String's UTF8 encoding.
     */
    static int getUTF8Length(String s) {
        int count = 0;
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            if (ch <= 0x7f) {
                count++;
            } else if (ch <= 0x7ff) {
                count += 2;
            } else {
                count += 3;
            }
        }
        return count;
    }

    /*
     * Returns an array of bytes representing the UTF8 encoding
     * of the specified String.
     */
    private static byte[] getUTF8Bytes(String s) {
    char[] c = s.toCharArray();
    int len = c.length;
    // Count the number of encoded bytes...
    int count = 0;
    for (int i = 0; i < len; i++) {
        int ch = c[i];
        if (ch <= 0x7f) {
        count++;
        } else if (ch <= 0x7ff) {
        count += 2;
        } else {
        count += 3;
        }
    }
    // Now return the encoded bytes...
    byte[] b = new byte[count];
    int off = 0;
    for (int i = 0; i < len; i++) {
        int ch = c[i];
        if (ch <= 0x7f) {
        b[off++] = (byte)ch;
        } else if (ch <= 0x7ff) {
        b[off++] = (byte)((ch >> 6) | 0xc0);
        b[off++] = (byte)((ch & 0x3f) | 0x80);
        } else {
        b[off++] = (byte)((ch >> 12) | 0xe0);
        b[off++] = (byte)(((ch >> 6) & 0x3f) | 0x80);
        b[off++] = (byte)((ch & 0x3f) | 0x80);
        }
    }
    return b;
    }
}

Ok, thanks for all your suggestions, but I was able to get it working like I wanted.... it CAN be done, you CAN add files after closing the file, as long as you save your place!!! :-D

Here's how I was able to get it going working:

        try  {
            for(int i=0; i < _files.size(); i++) {
                //beginning of initial setup stuff
                BufferedInputStream origin = null;
                FileOutputStream dest = new FileOutputStream(_zipFile,true); 
                ZipOutputStreamNew out = new ZipOutputStreamNew(new BufferedOutputStream(dest));
                byte data[] = new byte[BUFFER];
                if (havePreviousData) {
                    out.setWritten(tempWritten);
                    out.setXentries(tempXentries);
                }
                //end of initial setup stuff

                //beginning of for loop
                Log.i("Compress", "Adding: " + _files.get(i));
                FileInputStream fi = new FileInputStream(_files.get(i)); 
                origin = new BufferedInputStream(fi, BUFFER);
                TempString = _files.get(i).substring(_files.get(i).lastIndexOf("/") + 1);
                ZipEntry entry = new ZipEntry(_paths.get(i) + TempString);
                out.putNextEntry(entry);
                int count;
                while ((count = origin.read(data, 0, BUFFER)) != -1) { 
                    out.write(data, 0, count); 
                }
                origin.close();
                out.closeEntry();
                //end of for loop

                //beginning of finishing stuff
                if (i == (_files.size()-1)) {
                    //it's the last record so we should finish it off
                    out.closeAndFinish();
                } else {
                    //close the file, but don't write the Central Directory
                    //first, back up where the zip file was...
                    tempWritten = out.getWritten();
                    tempXentries = out.getXentries();
                    havePreviousData = true;
                    //now close the file
                    out.close();
                }
                //end of finishing stuff
            }
            //zip succeeded
    } catch(Exception e) {
        Log.e("ZipCreation", "Error writing zip", e);
        e.printStackTrace();
    }

Also, keep in mind, this is not the only code I had to do. I also had to make my own copy of ZipOutputStream so that I could expose the following functions that I created within my ZipOutputStreamNew class....

getWritten()
getXentries()

as well as

setWritten(long mWritten)
setXentries(Vector<XEntry> mXEntries)

For the most part, all this does, is it starts writing like normal, then, instead of closing like normal, it backs up those two variables, and then for the next iteration, it restores just those variables.

Let me know if you have any questions about all this, but I knew it would work, all it has to do is save where it was. :-D

Thanks again for all the help everybody! :-)

At Raj's request, here is the source code for ZipOutputStreamNew:

/**
 * 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
 * @version %I%, %G%
 */
public class ZipOutputStreamNew extends DeflaterOutputStream implements ZipConstants {
    public static class XEntry {
        public final ZipEntry entry;
        public final long offset;
        public final int flag;
        public XEntry(ZipEntry entry, long offset) {
            this.entry = entry;
            this.offset = offset;
            this.flag = (entry.getMethod() == DEFLATED &&
                 (entry.getSize() == -1 ||
                  entry.getCompressedSize() == -1 ||
                  entry.getCrc() == -1))
            // store size, compressed size, and crc-32 in data descriptor
            // immediately following the compressed entry data
            ? 8
            // store size, compressed size, and crc-32 in LOC header
            : 0;
        }
    }

    private XEntry current;
    private Vector<XEntry> xentries = new Vector<XEntry>();
    private HashSet<String> names = new HashSet<String>();
    private CRC32 crc = new CRC32();
    private long written = 0;
    private long locoff = 0;
    private String comment;
    private int method = DEFLATED;
    private boolean finished;

    private boolean closed = false;
    private boolean closeItPermanently = false;

    private static int version(ZipEntry e) throws ZipException {
    switch (e.getMethod()) {
    case DEFLATED: return 20;
    case STORED:   return 10;
    default: throw new ZipException("unsupported compression method");
    }
    }

    /**
     * Checks to make sure that this stream has not been closed.
     */
    private void ensureOpen() throws IOException {
    if (closed) {
        throw new IOException("Stream closed");
        }
    }
    /**
     * Compression method for uncompressed (STORED) entries.
     */
    public static final int STORED = ZipEntry.STORED;

    /**
     * Compression method for compressed (DEFLATED) entries.
     */
    public static final int DEFLATED = ZipEntry.DEFLATED;

    /**
     * Creates a new ZIP output stream.
     * @param out the actual output stream
     */
    public ZipOutputStreamNew(OutputStream out) {
    super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
        usesDefaultDeflater = true;
    }

    /**
     * Sets the ZIP file comment.
     * @param comment the comment string
     * @exception IllegalArgumentException if the length of the specified
     *        ZIP file comment is greater than 0xFFFF bytes
     */
    public void setComment(String comment) {
        if (comment != null && comment.length() > 0xffff/3
                                           && getUTF8Length(comment) > 0xffff) {
        throw new IllegalArgumentException("ZIP file comment too long.");
    }
    this.comment = comment;
    }

    /**
     * Sets the default compression method for subsequent entries. This
     * default will be used whenever the compression method is not specified
     * for an individual ZIP file entry, and is initially set to DEFLATED.
     * @param method the default compression method
     * @exception IllegalArgumentException if the specified compression method
     *        is invalid
     */
    public void setMethod(int method) {
    if (method != DEFLATED && method != STORED) {
        throw new IllegalArgumentException("invalid compression method");
    }
    this.method = method;
    }

    /**
     * Sets the compression level for subsequent entries which are DEFLATED.
     * The default setting is DEFAULT_COMPRESSION.
     * @param level the compression level (0-9)
     * @exception IllegalArgumentException if the compression level is invalid
     */
    public void setLevel(int level) {
    def.setLevel(level);
    }

    /**
     * Begins writing a new ZIP file entry and positions the stream to the
     * start of the entry data. Closes the current entry if still active.
     * The default compression method will be used if no compression method
     * was specified for the entry, and the current time will be used if
     * the entry has no set modification time.
     * @param e the ZIP entry to be written
     * @exception ZipException if a ZIP format error has occurred
     * @exception IOException if an I/O error has occurred
     */
    public void putNextEntry(ZipEntry e) throws IOException {
    ensureOpen();
    if (current != null) {
        closeEntry();   // close previous entry
    }
    if (e.getTime() == -1) {
        e.setTime(System.currentTimeMillis());
    }
    if (e.getMethod() == -1) {
        e.setMethod(method);    // use default method
    }
    switch (e.getMethod()) {
    case DEFLATED:
        break;
    case STORED:
        // compressed size, uncompressed size, and crc-32 must all be
        // set for entries using STORED compression method
        if (e.getSize() == -1) {
        e.setSize(e.getCompressedSize());
        } else if (e.getCompressedSize() == -1) {
        e.setCompressedSize(e.getSize());
        } else if (e.getSize() != e.getCompressedSize()) {
        throw new ZipException(
            "STORED entry where compressed != uncompressed size");
        }
        if (e.getSize() == -1 || e.getCrc() == -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.getName())) {
        throw new ZipException("duplicate entry: " + e.getName());
    }
    current = new XEntry(e, written);
    xentries.add(current);
        writeLOC(current);
    }

    /**
     * Closes the current ZIP entry and positions the stream for writing
     * the next entry.
     * @exception ZipException if a ZIP format error has occurred
     * @exception IOException if an I/O error has occurred
     */
    public void closeEntry() throws IOException {
    ensureOpen();
    if (current != null) {
        ZipEntry e = current.entry;
        switch (e.getMethod()) {
        case DEFLATED:
        def.finish();
        while (!def.finished()) {
            deflate();
        }
        if ((current.flag & 8) == 0) {
            // verify size, compressed size, and crc-32 settings
            if (e.getSize() != def.getBytesRead()) {
            throw new ZipException(
                "invalid entry size (expected " + e.getSize() +
                " but got " + def.getBytesRead() + " bytes)");
            }
            if (e.getCompressedSize() != def.getBytesWritten()) {
            throw new ZipException(
                "invalid entry compressed size (expected " +
                e.getCompressedSize() + " but got " + def.getBytesWritten() + " bytes)");
            }
            if (e.getCrc() != crc.getValue()) {
            throw new ZipException(
                "invalid entry CRC-32 (expected 0x" +
                Long.toHexString(e.getCrc()) + " but got 0x" +
                Long.toHexString(crc.getValue()) + ")");
            }
        } else {
            e.setSize(def.getBytesRead());
            e.setCompressedSize(def.getBytesWritten());
            e.setCrc(crc.getValue());
            writeEXT(e);
        }
        def.reset();
        written += e.getCompressedSize();
        break;
        case STORED:
        // we already know that both e.size and e.csize are the same
        if (e.getSize() != written - locoff) {
            throw new ZipException(
            "invalid entry size (expected " + e.getSize() +
            " but got " + (written - locoff) + " bytes)");
        }
        if (e.getCrc() != crc.getValue()) {
            throw new ZipException(
             "invalid entry crc-32 (expected 0x" +
             Long.toHexString(e.getCrc()) + " but got 0x" +
             Long.toHexString(crc.getValue()) + ")");
        }
        break;
        default:
        throw new ZipException("invalid compression method");
        }
        crc.reset();
        current = null;
    }
    }

    /**
     * Writes an array of bytes to the current ZIP entry data. This method
     * will block until all the bytes are written.
     * @param b the data to be written
     * @param off the start offset in the data
     * @param len the number of bytes that are written
     * @exception ZipException if a ZIP file error has occurred
     * @exception IOException if an I/O error has occurred
     */
    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.getMethod()) {
    case DEFLATED:
        super.write(b, off, len);
        break;
    case STORED:
        written += len;
        if (written - locoff > entry.getSize()) {
        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);
    }

    /**
     * Finishes writing the contents of the ZIP output stream without closing
     * the underlying stream. Use this method when applying multiple filters
     * in succession to the same output stream.
     * @exception ZipException if a ZIP file error has occurred
     * @exception IOException if an I/O exception has occurred
     */
    public void finish() throws IOException {
        ensureOpen();
        if (finished) {
            return;
        }
        if (current != null) {
            closeEntry();
        }
        if (xentries.size() < 1) {
            throw new ZipException("ZIP file must have at least one entry");
        }
        if (closeItPermanently) {
            // write central directory
            long off = written;
            for (XEntry xentry : xentries)
                writeCEN(xentry);
            writeEND(off, written - off);
            finished = true;
            //Log.e("ZipOutputStreamNew", "I just ran wrote the Central Directory Jared!");
        }
        //Log.e("ZipOutputStreamNew", "I just ran finish() Jared!");
    }

    /**
     * Gets the value of the "xentries" variable (for later use)
     * @return
     */
    public Vector<XEntry> getXentries() {
        return xentries;

        //TODO convert this to primitive data types
    }

    /**
     * Gets the value of the "written" variable (for later use)
     * @return
     */
    public long getWritten() {
        return written;
    }


    /**
     * Sets the value of the "xentries" variable (for later use)
     * @return
     */
    public void setXentries(Vector<XEntry> mXEntries) {
        xentries = mXEntries;
        //TODO convert this to primitive data types
    }

    /**
     * Sets the value of the "written" variable (for later use)
     * @return
     */
    public void setWritten(long mWritten) {
        written = mWritten;
    }


    /**
     * Closes the ZIP output stream as well as the stream being filtered.
     * @exception ZipException if a ZIP file error has occurred
     * @exception IOException if an I/O error has occurred
     */
    public void closeAndFinish() throws IOException {
        if (!closed) {
            closeItPermanently=true;
            super.close();
            closed = true;
        }
    }

    /**
     * Used to close the ZIP output stream as well as the stream being filtered.
     * instead it does nothing :-P
     * @exception ZipException if a ZIP file error has occurred
     * @exception IOException if an I/O error has occurred
     */
    public void close() throws IOException {
        if (!closed) {
            closeItPermanently=false;
            super.close();
            closed = true;
        }
    }

    /*
     * Writes local file (LOC) header for specified entry.
     */
    private void writeLOC(XEntry xentry) throws IOException {
    ZipEntry e = xentry.entry;
    int flag = xentry.flag;
    writeInt(LOCSIG);       // LOC header signature
    writeShort(version(e));     // version needed to extract
    writeShort(flag);           // general purpose bit flag
    writeShort(e.getMethod());       // compression method
    writeInt(e.getTime());           // last modification time
    if ((flag & 8) == 8) {
        // store size, uncompressed size, and crc-32 in data descriptor
        // immediately following compressed entry data
        writeInt(0);
        writeInt(0);
        writeInt(0);
    } else {
        writeInt(e.getCrc());        // crc-32
        writeInt(e.getCompressedSize());      // compressed size
        writeInt(e.getSize());       // uncompressed size
    }
    byte[] nameBytes = getUTF8Bytes(e.getName());
    writeShort(nameBytes.length);
    writeShort(e.getExtra() != null ? e.getExtra().length : 0);
    writeBytes(nameBytes, 0, nameBytes.length);
    if (e.getExtra() != null) {
        writeBytes(e.getExtra(), 0, e.getExtra().length);
    }
    locoff = written;
    }

    /*
     * Writes extra data descriptor (EXT) for specified entry.
     */
    private void writeEXT(ZipEntry e) throws IOException {
    writeInt(EXTSIG);       // EXT header signature
    writeInt(e.getCrc());       // crc-32
    writeInt(e.getCompressedSize());        // compressed size
    writeInt(e.getSize());      // uncompressed size
    }

    /*
     * Write central directory (CEN) header for specified entry.
     * REMIND: add support for file attributes
     */
    private void writeCEN(XEntry xentry) throws IOException {
    ZipEntry e  = xentry.entry;
    int flag = xentry.flag;
    int version = version(e);
    writeInt(CENSIG);       // CEN header signature
    writeShort(version);        // version made by
    writeShort(version);        // version needed to extract
    writeShort(flag);       // general purpose bit flag
    writeShort(e.getMethod());      // compression method
    writeInt(e.getTime());      // last modification time
    writeInt(e.getCrc());       // crc-32
    writeInt(e.getCompressedSize());        // compressed size
    writeInt(e.getSize());      // uncompressed size
    byte[] nameBytes = getUTF8Bytes(e.getName());
    writeShort(nameBytes.length);
    writeShort(e.getExtra() != null ? e.getExtra().length : 0);
    byte[] commentBytes;
    if (e.getComment() != null) {
        commentBytes = getUTF8Bytes(e.getComment());
        writeShort(commentBytes.length);
    } else {
        commentBytes = null;
        writeShort(0);
    }
    writeShort(0);          // starting disk number
    writeShort(0);          // internal file attributes (unused)
    writeInt(0);            // external file attributes (unused)
    writeInt(xentry.offset);    // relative offset of local header
    writeBytes(nameBytes, 0, nameBytes.length);
    if (e.getExtra() != null) {
        writeBytes(e.getExtra(), 0, e.getExtra().length);
    }
    if (commentBytes != null) {
        writeBytes(commentBytes, 0, commentBytes.length);
    }
    }

    /*
     * Writes end of central directory (END) header.
     */
    private void writeEND(long off, long len) throws IOException {
    int count = xentries.size();
    writeInt(ENDSIG);       // END record signature
    writeShort(0);          // number of this disk
    writeShort(0);          // central directory start disk
    writeShort(count);      // number of directory entries on disk
    writeShort(count);      // total number of directory entries
    writeInt(len);          // length of central directory
    writeInt(off);          // offset of central directory
    if (comment != null) {      // zip file comment
        byte[] b = getUTF8Bytes(comment);
        writeShort(b.length);
        writeBytes(b, 0, b.length);
    } else {
        writeShort(0);
    }
    }

    /*
     * Writes a 16-bit short to the output stream in little-endian byte order.
     */
    private void writeShort(int v) throws IOException {
    OutputStream out = this.out;
    out.write((v >>> 0) & 0xff);
    out.write((v >>> 8) & 0xff);
    written += 2;
    }

    /*
     * Writes a 32-bit int to the output stream in little-endian byte order.
     */
    private void writeInt(long v) throws IOException {
    OutputStream out = this.out;
    out.write((int)((v >>>  0) & 0xff));
    out.write((int)((v >>>  8) & 0xff));
    out.write((int)((v >>> 16) & 0xff));
    out.write((int)((v >>> 24) & 0xff));
    written += 4;
    }

    /*
     * Writes an array of bytes to the output stream.
     */
    private void writeBytes(byte[] b, int off, int len) throws IOException {
    super.out.write(b, off, len);
    written += len;
    }

    /*
     * Returns the length of String's UTF8 encoding.
     */
    static int getUTF8Length(String s) {
        int count = 0;
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            if (ch <= 0x7f) {
                count++;
            } else if (ch <= 0x7ff) {
                count += 2;
            } else {
                count += 3;
            }
        }
        return count;
    }

    /*
     * Returns an array of bytes representing the UTF8 encoding
     * of the specified String.
     */
    private static byte[] getUTF8Bytes(String s) {
    char[] c = s.toCharArray();
    int len = c.length;
    // Count the number of encoded bytes...
    int count = 0;
    for (int i = 0; i < len; i++) {
        int ch = c[i];
        if (ch <= 0x7f) {
        count++;
        } else if (ch <= 0x7ff) {
        count += 2;
        } else {
        count += 3;
        }
    }
    // Now return the encoded bytes...
    byte[] b = new byte[count];
    int off = 0;
    for (int i = 0; i < len; i++) {
        int ch = c[i];
        if (ch <= 0x7f) {
        b[off++] = (byte)ch;
        } else if (ch <= 0x7ff) {
        b[off++] = (byte)((ch >> 6) | 0xc0);
        b[off++] = (byte)((ch & 0x3f) | 0x80);
        } else {
        b[off++] = (byte)((ch >> 12) | 0xe0);
        b[off++] = (byte)(((ch >> 6) & 0x3f) | 0x80);
        b[off++] = (byte)((ch & 0x3f) | 0x80);
        }
    }
    return b;
    }
}
标点 2024-11-09 14:52:58

我相信目前的 API 还无法做到这一点。
您可以将数据附加到任何文件,但这并不意味着您最终会得到正确的文件格式。 .zip 文件与 .tar 文件不同,压缩要求对文件的处理(文件位置、EOF 等)施加限制。如果您考虑文件格式的结构(取自维基百科此处 )你就会明白为什么仅仅附加不起作用。

有一个名为 TrueZip 的库可以工作,尽管我不知道它是否支持 android。看看另一个类似问题中的答案:
使用 Java 将文件附加到 zip 文件

另外,作为解决方法,您可以创建单独的 .zip 文件并将其附加为 tarball (此处的文件格式)。压缩可能稍微差一些,但就时间效率而言会好得多。


根据评论(和可能的解决方案)进行更新

您可以单独添加每个 ZipEntry 并让 ZipOutputStream 对象保持打开状态,只要您仍在拍照。不过,我可以看到这种方法的风险,因为应用程序在拍照时出现任何问题(强行关闭、电池耗尽等)都可能导致整个文件无法使用。您需要确保使用正确的 try/catch/finally 块来关闭文件,并在发生 onClose()等事件时调用 closeZip() onDestroy(),但想法如下:

import java.io.*;
import java.util.zip.*;

public class Zip {
    static final int BUFFER = 2048;

    ZipOutputStream out;
    byte data[];

    public Zip(String name) {
        FileOutputStream dest = new FileOutputStream(name);
        out = new ZipOutputStream(new BufferedOutputStream(dest));
        data = new byte[BUFFER];
    }

    public void addFile (String name) {
        FileInputStream fi = new FileInputStream(name);
        BufferedInputStream origin = new BufferedInputStream(fi, BUFFER);
        ZipEntry entry = new ZipEntry(name);
        out.putNextEntry(entry);
        int count;
        while((count = origin.read(data, 0, BUFFER)) != -1) {
           out.write(data, 0, count);
        }
        origin.close();
     }

    public void closeZip () {
        out.close();
    }
}

I believe it can't be done right now with the current API.
You can append data to any file, but that does not mean that you will end up with the right file format. A .zip file is not like a .tar file, and the compression requires imposes restrictions to the handling of the files (file positions, EOF, etc.). If you consider the structure of the file format (taken from wikipedia here) you will understand why just appending does not work.

There is a library called TrueZip that could work, although I do not know if it supports android. Take a look at this answer in another similar question:
Appending files to a zip file with Java .

Also, as a workaround, you could create individual .zip files and append them as a tarball (file format here). Compression might be slighty worst, but it would be much better in terms of time efficiency.


Update based on the comments (and possible solution)

You could separate the addition to each ZipEntry and leave the ZipOutputStream object open as long as you are still taking pictures. I can see risks with that approach, though, as any problem with the app while still taking pictures (a force close, run out of battery, etc) may render the whole file unusable. You will need to make sure to use the right try/catch/finally blocks to close the file and call closeZip() upon events such as onClose() and onDestroy(), but the idea would be the following:

import java.io.*;
import java.util.zip.*;

public class Zip {
    static final int BUFFER = 2048;

    ZipOutputStream out;
    byte data[];

    public Zip(String name) {
        FileOutputStream dest = new FileOutputStream(name);
        out = new ZipOutputStream(new BufferedOutputStream(dest));
        data = new byte[BUFFER];
    }

    public void addFile (String name) {
        FileInputStream fi = new FileInputStream(name);
        BufferedInputStream origin = new BufferedInputStream(fi, BUFFER);
        ZipEntry entry = new ZipEntry(name);
        out.putNextEntry(entry);
        int count;
        while((count = origin.read(data, 0, BUFFER)) != -1) {
           out.write(data, 0, count);
        }
        origin.close();
     }

    public void closeZip () {
        out.close();
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文