如何将文件附加到现有的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文件中,并完成zip文件?我甚至考虑过可能采用ZipOutputStream并修改它以适合我需要的这个模型。逻辑上应该是可能的?: -)
提前感谢您的帮助!: - d
jared
好的,谢谢你所有的建议,但我能够得到它的工作,就像我想....这是可以做到的,你可以在关闭文件后添加文件,只要你保存你的地方!!: - 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;
}
}
我认为目前的API无法做到这一点。
您可以向任何文件追加数据,但这并不意味着您最终将得到正确的文件格式。.zip
文件不像.tar
文件,压缩要求对文件的处理施加限制(文件位置、EOF等)。如果你考虑一下文件格式的结构(取自维基百科),你就会明白为什么只是附加不起作用。
有一个叫做TrueZip的库可以工作,虽然我不知道它是否支持android。看看另一个类似问题的答案:使用Java将文件附加到zip文件中。
另外,作为一种解决方法,您可以创建单独的.zip
文件并将它们附加为tarball(这里的文件格式)。压缩可能会稍微糟糕一点,但在时间效率方面会好得多。
根据注释(和可能的解决方案)更新
您可以将每个ZipEntry
的添加分开,并且只要您还在拍照,就可以将ZipOutputStream
对象打开。不过,我可以看到这种方法的风险,因为在拍照时应用程序的任何问题(强制关闭,电池耗尽等)都可能导致整个文件无法使用。您需要确保使用正确的try/catch/finally块来关闭文件,并在onClose()
和onDestroy()
等事件时调用closeZip()
,但思路如下:
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();
}
}