在获得一个以Zip格式压缩的文件之后,需要将其进行解压缩,还原成压缩前的文件。若是使用Java自带的压缩工具包来实现解压缩文件到指定文件夹的功能,因为jdk提供的zip只能按UTF-8格式处理,而Windows系统中文件名是以GBK方式编码的,所以如果是解压一个包含中文文件名的zip包,会报非法参数异常,如图所示:
所以要实现解压缩,就得对DeflaterOutputStream.java、InflaterInputStream.java、ZipConstants.java、ZipEntry.java、ZipInputStream.java以及ZipOutputStream.java这些相关的类进行修改,过程如下:
DeflaterOutputStream.java:
package cn.edu.xdian.crytoll; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.zip.Deflater; /** * This class implements an output stream filter for compressing data in * the "deflate" compression format. It is also used as the basis for other * types of compression filters, such as GZIPOutputStream. * * @see Deflater * @version 1.36, 03/13/06 * @author David Connelly */ public class DeflaterOutputStream extends FilterOutputStream { /** * Compressor for this stream. */ protected Deflater def; /** * Output buffer for writing compressed data. */ protected byte[] buf; /** * Indicates that the stream has been closed. */ private boolean closed = false; /** * Creates a new output stream with the specified compressor and * buffer size. * @param out the output stream * @param def the compressor ("deflater") * @param size the output buffer size * @exception IllegalArgumentException if size is <= 0 */ public DeflaterOutputStream(OutputStream out, Deflater def, int size) { super(out); if (out == null || def == null) { throw new NullPointerException(); } else if (size <= 0) { throw new IllegalArgumentException("buffer size <= 0"); } this.def = def; buf = new byte[size]; } /** * Creates a new output stream with the specified compressor and * a default buffer size. * @param out the output stream * @param def the compressor ("deflater") */ public DeflaterOutputStream(OutputStream out, Deflater def) { this(out, def, 512); } boolean usesDefaultDeflater = false; /** * Creates a new output stream with a default compressor and buffer size. * @param out the output stream */ public DeflaterOutputStream(OutputStream out) { this(out, new Deflater()); usesDefaultDeflater = true; } /** * Writes a byte to the compressed output stream. This method will * block until the byte can be written. * @param b the byte to be written * @exception IOException if an I/O error has occurred */ public void write(int b) throws IOException { byte[] buf = new byte[1]; buf[0] = (byte)(b & 0xff); write(buf, 0, 1); } /** * Writes an array of bytes to the compressed output stream. This * method will block until all the bytes are written. * @param b the data to be written * @param off the start offset of the data * @param len the length of the data * @exception IOException if an I/O error has occurred */ public void write(byte[] b, int off, int len) throws IOException { if (def.finished()) { throw new IOException("write beyond end of stream"); } if ((off | len | (off + len) | (b.length - (off + len))) < 0) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return; } if (!def.finished()) { // Deflate no more than stride bytes at a time. This avoids // excess copying in deflateBytes (see Deflater.c) int stride = buf.length; for (int i = 0; i < len; i+= stride) { def.setInput(b, off + i, Math.min(stride, len - i)); while (!def.needsInput()) { deflate(); } } } } /** * Finishes writing compressed data to the output stream without closing * the underlying stream. Use this method when applying multiple filters * in succession to the same output stream. * @exception IOException if an I/O error has occurred */ public void finish() throws IOException { if (!def.finished()) { def.finish(); while (!def.finished()) { deflate(); } } } /** * Writes remaining compressed data to the output stream and closes the * underlying stream. * @exception IOException if an I/O error has occurred */ public void close() throws IOException { if (!closed) { finish(); if (usesDefaultDeflater) def.end(); out.close(); closed = true; } } /** * Writes next block of compressed data to the output stream. * @throws IOException if an I/O error has occurred */ protected void deflate() throws IOException { int len = def.deflate(buf, 0, buf.length); if (len > 0) { out.write(buf, 0, len); } } }
3. 在包内新建InflaterInputStream类,代码如下:
InflaterInputStream.java:
package cn.edu.xdian.crytoll; import java.io.EOFException; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.util.zip.DataFormatException; import java.util.zip.Inflater; import java.util.zip.ZipException; /** * This class implements a stream filter for uncompressing data in the * "deflate" compression format. It is also used as the basis for other * decompression filters, such as GZIPInputStream. * * @see Inflater * @version 1.40, 04/07/06 * @author David Connelly */ public class InflaterInputStream extends FilterInputStream { /** * Decompressor for this stream. */ protected Inflater inf; /** * Input buffer for decompression. */ protected byte[] buf; /** * Length of input buffer. */ protected int len; private boolean closed = false; // this flag is set to true after EOF has reached private boolean reachEOF = false; /** * Check to make sure that this stream has not been closed */ private void ensureOpen() throws IOException { if (closed) { throw new IOException("Stream closed"); } } /** * Creates a new input stream with the specified decompressor and * buffer size. * @param in the input stream * @param inf the decompressor ("inflater") * @param size the input buffer size * @exception IllegalArgumentException if size is <= 0 */ public InflaterInputStream(InputStream in, Inflater inf, int size) { super(in); if (in == null || inf == null) { throw new NullPointerException(); } else if (size <= 0) { throw new IllegalArgumentException("buffer size <= 0"); } this.inf = inf; buf = new byte[size]; } /** * Creates a new input stream with the specified decompressor and a * default buffer size. * @param in the input stream * @param inf the decompressor ("inflater") */ public InflaterInputStream(InputStream in, Inflater inf) { this(in, inf, 512); } boolean usesDefaultInflater = false; /** * Creates a new input stream with a default decompressor and buffer size. * @param in the input stream */ public InflaterInputStream(InputStream in) { this(in, new Inflater()); usesDefaultInflater = true; } private byte[] singleByteBuf = new byte[1]; /** * Reads a byte of uncompressed data. This method will block until * enough input is available for decompression. * @return the byte read, or -1 if end of compressed input is reached * @exception IOException if an I/O error has occurred */ public int read() throws IOException { ensureOpen(); return read(singleByteBuf, 0, 1) == -1 ? -1 : singleByteBuf[0] & 0xff; } /** * Reads uncompressed data into an array of bytes. If <code>len</code> is not * zero, the method will block until some input can be decompressed; otherwise, * no bytes are read and <code>0</code> is returned. * @param b the buffer into which the data is read * @param off the start offset in the destination array <code>b</code> * @param len the maximum number of bytes read * @return the actual number of bytes read, or -1 if the end of the * compressed input is reached or a preset dictionary is needed * @exception NullPointerException If <code>b</code> is <code>null</code>. * @exception IndexOutOfBoundsException If <code>off</code> is negative, * <code>len</code> is negative, or <code>len</code> is greater than * <code>b.length - off</code> * @exception ZipException if a ZIP format error has occurred * @exception IOException if an I/O error has occurred */ public int read(byte[] b, int off, int len) throws IOException { ensureOpen(); if (b == null) { throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } try { int n; while ((n = inf.inflate(b, off, len)) == 0) { if (inf.finished() || inf.needsDictionary()) { reachEOF = true; return -1; } if (inf.needsInput()) { fill(); } } return n; } catch (DataFormatException e) { String s = e.getMessage(); throw new ZipException(s != null ? s : "Invalid ZLIB data format"); } } /** * Returns 0 after EOF has been reached, otherwise always return 1. * <p> * Programs should not count on this method to return the actual number * of bytes that could be read without blocking. * * @return 1 before EOF and 0 after EOF. * @exception IOException if an I/O error occurs. * */ public int available() throws IOException { ensureOpen(); if (reachEOF) { return 0; } else { return 1; } } private byte[] b = new byte[512]; /** * Skips specified number of bytes of uncompressed data. * @param n the number of bytes to skip * @return the actual number of bytes skipped. * @exception IOException if an I/O error has occurred * @exception IllegalArgumentException if n < 0 */ public long skip(long n) throws IOException { if (n < 0) { throw new IllegalArgumentException("negative skip length"); } ensureOpen(); int max = (int)Math.min(n, Integer.MAX_VALUE); int total = 0; while (total < max) { int len = max - total; if (len > b.length) { len = b.length; } len = read(b, 0, len); if (len == -1) { reachEOF = true; break; } total += len; } return total; } /** * Closes this input stream and releases any system resources associated * with the stream. * @exception IOException if an I/O error has occurred */ public void close() throws IOException { if (!closed) { if (usesDefaultInflater) inf.end(); in.close(); closed = true; } } /** * Fills input buffer with more data to decompress. * @exception IOException if an I/O error has occurred */ protected void fill() throws IOException { ensureOpen(); len = in.read(buf, 0, buf.length); if (len == -1) { throw new EOFException("Unexpected end of ZLIB input stream"); } inf.setInput(buf, 0, len); } /** * Tests if this input stream supports the <code>mark</code> and * <code>reset</code> methods. The <code>markSupported</code> * method of <code>InflaterInputStream</code> returns * <code>false</code>. * * @return a <code>boolean</code> indicating if this stream type supports * the <code>mark</code> and <code>reset</code> methods. * @see java.io.InputStream#mark(int) * @see java.io.InputStream#reset() */ public boolean markSupported() { return false; } /** * Marks the current position in this input stream. * * <p> The <code>mark</code> method of <code>InflaterInputStream</code> * does nothing. * * @param readlimit the maximum limit of bytes that can be read before * the mark position becomes invalid. * @see java.io.InputStream#reset() */ public synchronized void mark(int readlimit) { } /** * Repositions this stream to the position at the time the * <code>mark</code> method was last called on this input stream. * * <p> The method <code>reset</code> for class * <code>InflaterInputStream</code> does nothing except throw an * <code>IOException</code>. * * @exception IOException if this method is invoked. * @see java.io.InputStream#mark(int) * @see java.io.IOException */ public synchronized void reset() throws IOException { throw new IOException("mark/reset not supported"); } }
4. 在包中新建ZipConstants接口,代码如下:
ZipConstants.java:
package cn.edu.xdian.crytoll; interface ZipConstants { /* * Header signatures */ static long LOCSIG = 0x04034b50L; // "PK\003\004" static long EXTSIG = 0x08074b50L; // "PK\007\008" static long CENSIG = 0x02014b50L; // "PK\001\002" static long ENDSIG = 0x06054b50L; // "PK\005\006" /* * Header sizes in bytes (including signatures) */ static final int LOCHDR = 30; // LOC header size static final int EXTHDR = 16; // EXT header size static final int CENHDR = 46; // CEN header size static final int ENDHDR = 22; // END header size /* * Local file (LOC) header field offsets */ static final int LOCVER = 4; // version needed to extract static final int LOCFLG = 6; // general purpose bit flag static final int LOCHOW = 8; // compression method static final int LOCTIM = 10; // modification time static final int LOCCRC = 14; // uncompressed file crc-32 value static final int LOCSIZ = 18; // compressed size static final int LOCLEN = 22; // uncompressed size static final int LOCNAM = 26; // filename length static final int LOCEXT = 28; // extra field length /* * Extra local (EXT) header field offsets */ static final int EXTCRC = 4; // uncompressed file crc-32 value static final int EXTSIZ = 8; // compressed size static final int EXTLEN = 12; // uncompressed size /* * Central directory (CEN) header field offsets */ static final int CENVEM = 4; // version made by static final int CENVER = 6; // version needed to extract static final int CENFLG = 8; // encrypt, decrypt flags static final int CENHOW = 10; // compression method static final int CENTIM = 12; // modification time static final int CENCRC = 16; // uncompressed file crc-32 value static final int CENSIZ = 20; // compressed size static final int CENLEN = 24; // uncompressed size static final int CENNAM = 28; // filename length static final int CENEXT = 30; // extra field length static final int CENCOM = 32; // comment length static final int CENDSK = 34; // disk number start static final int CENATT = 36; // internal file attributes static final int CENATX = 38; // external file attributes static final int CENOFF = 42; // LOC header offset /* * End of central directory (END) header field offsets */ static final int ENDSUB = 8; // number of entries on this disk static final int ENDTOT = 10; // total number of entries static final int ENDSIZ = 12; // central directory size in bytes static final int ENDOFF = 16; // offset of first CEN header static final int ENDCOM = 20; // zip file comment length }
ZipEntry.java:
package cn.edu.xdian.crytoll; import java.util.Date; /** * This class is used to represent a ZIP file entry. * * @version 1.42, 01/02/08 * @author David Connelly */ public class ZipEntry implements ZipConstants, Cloneable { String name; // entry name long time = -1; // modification time (in DOS time) long crc = -1; // crc-32 of entry data long size = -1; // uncompressed size of entry data long csize = -1; // compressed size of entry data int method = -1; // compression method byte[] extra; // optional extra field data for entry String comment; // optional comment string for entry /** * Compression method for uncompressed entries. */ public static final int STORED = 0; /** * Compression method for compressed (deflated) entries. */ public static final int DEFLATED = 8; static { /* Zip library is loaded from System.initializeSystemClass */ //initIDs(); } private static native void initIDs(); /** * Creates a new zip entry with the specified name. * * @param name the entry name * @exception NullPointerException if the entry name is null * @exception IllegalArgumentException if the entry name is longer than * 0xFFFF bytes */ public ZipEntry(String name) { if (name == null) { throw new NullPointerException(); } if (name.length() > 0xFFFF) { throw new IllegalArgumentException("entry name too long"); } this.name = name; } /** * Creates a new zip entry with fields taken from the specified * zip entry. * @param e a zip Entry object */ public ZipEntry(ZipEntry e) { name = e.name; time = e.time; crc = e.crc; size = e.size; csize = e.csize; method = e.method; extra = e.extra; comment = e.comment; } /* * Creates a new zip entry for the given name with fields initialized * from the specified jzentry data. */ ZipEntry(String name, long jzentry) { this.name = name; initFields(jzentry); } private native void initFields(long jzentry); /* * Creates a new zip entry with fields initialized from the specified * jzentry data. */ ZipEntry(long jzentry) { initFields(jzentry); } /** * Returns the name of the entry. * @return the name of the entry */ public String getName() { return name; } /** * Sets the modification time of the entry. * @param time the entry modification time in number of milliseconds * since the epoch * @see #getTime() */ public void setTime(long time) { this.time = javaToDosTime(time); } /** * Returns the modification time of the entry, or -1 if not specified. * @return the modification time of the entry, or -1 if not specified * @see #setTime(long) */ public long getTime() { return time != -1 ? dosToJavaTime(time) : -1; } /** * Sets the uncompressed size of the entry data. * @param size the uncompressed size in bytes * @exception IllegalArgumentException if the specified size is less * than 0 or greater than 0xFFFFFFFF bytes * @see #getSize() */ public void setSize(long size) { if (size < 0 || size > 0xFFFFFFFFL) { throw new IllegalArgumentException("invalid entry size"); } this.size = size; } /** * Returns the uncompressed size of the entry data, or -1 if not known. * @return the uncompressed size of the entry data, or -1 if not known * @see #setSize(long) */ public long getSize() { return size; } /** * Returns the size of the compressed entry data, or -1 if not known. * In the case of a stored entry, the compressed size will be the same * as the uncompressed size of the entry. * @return the size of the compressed entry data, or -1 if not known * @see #setCompressedSize(long) */ public long getCompressedSize() { return csize; } /** * Sets the size of the compressed entry data. * @param csize the compressed size to set to * @see #getCompressedSize() */ public void setCompressedSize(long csize) { this.csize = csize; } /** * Sets the CRC-32 checksum of the uncompressed entry data. * @param crc the CRC-32 value * @exception IllegalArgumentException if the specified CRC-32 value is * less than 0 or greater than 0xFFFFFFFF * @see #getCrc() */ public void setCrc(long crc) { if (crc < 0 || crc > 0xFFFFFFFFL) { throw new IllegalArgumentException("invalid entry crc-32"); } this.crc = crc; } /** * Returns the CRC-32 checksum of the uncompressed entry data, or -1 if * not known. * @return the CRC-32 checksum of the uncompressed entry data, or -1 if * not known * @see #setCrc(long) */ public long getCrc() { return crc; } /** * Sets the compression method for the entry. * @param method the compression method, either STORED or DEFLATED * @exception IllegalArgumentException if the specified compression * method is invalid * @see #getMethod() */ public void setMethod(int method) { if (method != STORED && method != DEFLATED) { throw new IllegalArgumentException("invalid compression method"); } this.method = method; } /** * Returns the compression method of the entry, or -1 if not specified. * @return the compression method of the entry, or -1 if not specified * @see #setMethod(int) */ public int getMethod() { return method; } /** * Sets the optional extra field data for the entry. * @param extra the extra field data bytes * @exception IllegalArgumentException if the length of the specified * extra field data is greater than 0xFFFF bytes * @see #getExtra() */ public void setExtra(byte[] extra) { if (extra != null && extra.length > 0xFFFF) { throw new IllegalArgumentException("invalid extra field length"); } this.extra = extra; } /** * Returns the extra field data for the entry, or null if none. * @return the extra field data for the entry, or null if none * @see #setExtra(byte[]) */ public byte[] getExtra() { return extra; } /** * Sets the optional comment string for the entry. * @param comment the comment string * @exception IllegalArgumentException if the length of the specified * comment string is greater than 0xFFFF bytes * @see #getComment() */ public void setComment(String comment) { if (comment != null && comment.length() > 0xffff/3 && ZipOutputStream.getUTF8Length(comment) > 0xffff) { throw new IllegalArgumentException("invalid entry comment length"); } this.comment = comment; } /** * Returns the comment string for the entry, or null if none. * @return the comment string for the entry, or null if none * @see #setComment(String) */ public String getComment() { return comment; } /** * Returns true if this is a directory entry. A directory entry is * defined to be one whose name ends with a '/'. * @return true if this is a directory entry */ public boolean isDirectory() { return name.endsWith("/"); } /** * Returns a string representation of the ZIP entry. */ public String toString() { return getName(); } /* * Converts DOS time to Java time (number of milliseconds since epoch). */ private static long dosToJavaTime(long dtime) { Date d = new Date((int)(((dtime >> 25) & 0x7f) + 80), (int)(((dtime >> 21) & 0x0f) - 1), (int)((dtime >> 16) & 0x1f), (int)((dtime >> 11) & 0x1f), (int)((dtime >> 5) & 0x3f), (int)((dtime << 1) & 0x3e)); return d.getTime(); } /* * Converts Java time to DOS time. */ private static long javaToDosTime(long time) { Date d = new Date(time); int year = d.getYear() + 1900; if (year < 1980) { return (1 << 21) | (1 << 16); } return (year - 1980) << 25 | (d.getMonth() + 1) << 21 | d.getDate() << 16 | d.getHours() << 11 | d.getMinutes() << 5 | d.getSeconds() >> 1; } /** * Returns the hash code value for this entry. */ public int hashCode() { return name.hashCode(); } /** * Returns a copy of this entry. */ public Object clone() { try { ZipEntry e = (ZipEntry)super.clone(); e.extra = (extra == null ? null : (byte[])extra.clone()); return e; } catch (CloneNotSupportedException e) { // This should never happen, since we are Cloneable throw new InternalError(); } } }
6. 在包中新建ZipInputStream类,代码如下:
ZipInputStream.java:
package cn.edu.xdian.crytoll; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.PushbackInputStream; import java.io.UnsupportedEncodingException; import java.util.zip.CRC32; import java.util.zip.Inflater; import java.util.zip.ZipException; /** * This class implements an input stream filter for reading files in the * ZIP file format. Includes support for both compressed and uncompressed * entries. * * @author David Connelly * @version 1.44, 06/15/07 */ public class ZipInputStream extends InflaterInputStream implements ZipConstants { private ZipEntry entry; private int flag; private CRC32 crc = new CRC32(); private long remaining; private byte[] tmpbuf = new byte[512]; private static final int STORED = ZipEntry.STORED; private static final int DEFLATED = ZipEntry.DEFLATED; private boolean closed = false; // this flag is set to true after EOF has reached for // one entry private boolean entryEOF = false; /** * Check to make sure that this stream has not been closed */ private void ensureOpen() throws IOException { if (closed) { throw new IOException("Stream closed"); } } /** * Creates a new ZIP input stream. * @param in the actual input stream */ public ZipInputStream(InputStream in) { super(new PushbackInputStream(in, 512), new Inflater(true), 512); usesDefaultInflater = true; if(in == null) { throw new NullPointerException("in is null"); } } /** * Reads the next ZIP file entry and positions the stream at the * beginning of the entry data. * @return the next ZIP file entry, or null if there are no more entries * @exception ZipException if a ZIP file error has occurred * @exception IOException if an I/O error has occurred */ public ZipEntry getNextEntry() throws IOException { ensureOpen(); if (entry != null) { closeEntry(); } crc.reset(); inf.reset(); if ((entry = readLOC()) == null) { return null; } if (entry.method == STORED) { remaining = entry.size; } entryEOF = false; return entry; } /** * Closes the current ZIP entry and positions the stream for reading the * next entry. * @exception ZipException if a ZIP file error has occurred * @exception IOException if an I/O error has occurred */ public void closeEntry() throws IOException { ensureOpen(); while (read(tmpbuf, 0, tmpbuf.length) != -1) ; entryEOF = true; } /** * Returns 0 after EOF has reached for the current entry data, * otherwise always return 1. * <p> * Programs should not count on this method to return the actual number * of bytes that could be read without blocking. * * @return 1 before EOF and 0 after EOF has reached for current entry. * @exception IOException if an I/O error occurs. * */ public int available() throws IOException { ensureOpen(); if (entryEOF) { return 0; } else { return 1; } } /** * Reads from the current ZIP entry into an array of bytes. * If <code>len</code> is not zero, the method * blocks until some input is available; otherwise, no * bytes are read and <code>0</code> is returned. * @param b the buffer into which the data is read * @param off the start offset in the destination array <code>b</code> * @param len the maximum number of bytes read * @return the actual number of bytes read, or -1 if the end of the * entry is reached * @exception NullPointerException If <code>b</code> is <code>null</code>. * @exception IndexOutOfBoundsException If <code>off</code> is negative, * <code>len</code> is negative, or <code>len</code> is greater than * <code>b.length - off</code> * @exception ZipException if a ZIP file error has occurred * @exception IOException if an I/O error has occurred */ public int read(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 0; } if (entry == null) { return -1; } switch (entry.method) { case DEFLATED: len = super.read(b, off, len); if (len == -1) { readEnd(entry); entryEOF = true; entry = null; } else { crc.update(b, off, len); } return len; case STORED: if (remaining <= 0) { entryEOF = true; entry = null; return -1; } if (len > remaining) { len = (int)remaining; } len = in.read(b, off, len); if (len == -1) { throw new ZipException("unexpected EOF"); } crc.update(b, off, len); remaining -= len; if (remaining == 0 && entry.crc != crc.getValue()) { throw new ZipException( "invalid entry CRC (expected 0x" + Long.toHexString(entry.crc) + " but got 0x" + Long.toHexString(crc.getValue()) + ")"); } return len; default: throw new ZipException("invalid compression method"); } } /** * Skips specified number of bytes in the current ZIP entry. * @param n the number of bytes to skip * @return the actual number of bytes skipped * @exception ZipException if a ZIP file error has occurred * @exception IOException if an I/O error has occurred * @exception IllegalArgumentException if n < 0 */ public long skip(long n) throws IOException { if (n < 0) { throw new IllegalArgumentException("negative skip length"); } ensureOpen(); int max = (int)Math.min(n, Integer.MAX_VALUE); int total = 0; while (total < max) { int len = max - total; if (len > tmpbuf.length) { len = tmpbuf.length; } len = read(tmpbuf, 0, len); if (len == -1) { entryEOF = true; break; } total += len; } return total; } /** * Closes this input stream and releases any system resources associated * with the stream. * @exception IOException if an I/O error has occurred */ public void close() throws IOException { if (!closed) { super.close(); closed = true; } } private byte[] b = new byte[256]; /* * Reads local file (LOC) header for next entry. */ private ZipEntry readLOC() throws IOException { try { readFully(tmpbuf, 0, LOCHDR); } catch (EOFException e) { return null; } if (get32(tmpbuf, 0) != LOCSIG) { return null; } // get the entry name and create the ZipEntry first int len = get16(tmpbuf, LOCNAM); int blen = b.length; if (len > blen) { do blen = blen * 2; while (len > blen); b = new byte[blen]; } readFully(b, 0, len); ZipEntry e = createZipEntry(getUTF8String(b, 0, len)); // now get the remaining fields for the entry flag = get16(tmpbuf, LOCFLG); if ((flag & 1) == 1) { throw new ZipException("encrypted ZIP entry not supported"); } e.method = get16(tmpbuf, LOCHOW); e.time = get32(tmpbuf, LOCTIM); if ((flag & 8) == 8) { /* "Data Descriptor" present */ if (e.method != DEFLATED) { throw new ZipException( "only DEFLATED entries can have EXT descriptor"); } } else { e.crc = get32(tmpbuf, LOCCRC); e.csize = get32(tmpbuf, LOCSIZ); e.size = get32(tmpbuf, LOCLEN); } len = get16(tmpbuf, LOCEXT); if (len > 0) { byte[] bb = new byte[len]; readFully(bb, 0, len); e.setExtra(bb); } return e; } /* * Fetches a UTF8-encoded String from the specified byte array. */ private static String getUTF8String(byte[] b, int off, int len) { try { String s = new String(b, off, len, "GBK"); return s; } catch (UnsupportedEncodingException e) { e.printStackTrace(); } //以上为新添加的解决GBK乱码的 // First, count the number of characters in the sequence int count = 0; int max = off + len; int i = off; while (i < max) { int c = b[i++] & 0xff; switch (c >> 4) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: // 0xxxxxxx count++; break; case 12: case 13: // 110xxxxx 10xxxxxx if ((int) (b[i++] & 0xc0) != 0x80) { throw new IllegalArgumentException(); } count++; break; case 14: // 1110xxxx 10xxxxxx 10xxxxxx if (((int) (b[i++] & 0xc0) != 0x80) || ((int) (b[i++] & 0xc0) != 0x80)) { throw new IllegalArgumentException(); } count++; break; default: // 10xxxxxx, 1111xxxx throw new IllegalArgumentException(); } } if (i != max) { throw new IllegalArgumentException(); } // Now decode the characters... char[] cs = new char[count]; i = 0; while (off < max) { int c = b[off++] & 0xff; switch (c >> 4) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: // 0xxxxxxx cs[i++] = (char) c; break; case 12: case 13: // 110xxxxx 10xxxxxx cs[i++] = (char) (((c & 0x1f) << 6) | (b[off++] & 0x3f)); break; case 14: // 1110xxxx 10xxxxxx 10xxxxxx int t = (b[off++] & 0x3f) << 6; cs[i++] = (char) (((c & 0x0f) << 12) | t | (b[off++] & 0x3f)); break; default: // 10xxxxxx, 1111xxxx throw new IllegalArgumentException(); } } return new String(cs, 0, count); } /** * Creates a new <code>ZipEntry</code> object for the specified * entry name. * * @param name the ZIP file entry name * @return the ZipEntry just created */ protected ZipEntry createZipEntry(String name) { return new ZipEntry(name); } /* * Reads end of deflated entry as well as EXT descriptor if present. */ private void readEnd(ZipEntry e) throws IOException { int n = inf.getRemaining(); if (n > 0) { ((PushbackInputStream)in).unread(buf, len - n, n); } if ((flag & 8) == 8) { /* "Data Descriptor" present */ readFully(tmpbuf, 0, EXTHDR); long sig = get32(tmpbuf, 0); if (sig != EXTSIG) { // no EXTSIG present e.crc = sig; e.csize = get32(tmpbuf, EXTSIZ - EXTCRC); e.size = get32(tmpbuf, EXTLEN - EXTCRC); ((PushbackInputStream)in).unread( tmpbuf, EXTHDR - EXTCRC - 1, EXTCRC); } else { e.crc = get32(tmpbuf, EXTCRC); e.csize = get32(tmpbuf, EXTSIZ); e.size = get32(tmpbuf, EXTLEN); } } if (e.size != inf.getBytesWritten()) { throw new ZipException( "invalid entry size (expected " + e.size + " but got " + inf.getBytesWritten() + " bytes)"); } if (e.csize != inf.getBytesRead()) { throw new ZipException( "invalid entry compressed size (expected " + e.csize + " but got " + inf.getBytesRead() + " bytes)"); } if (e.crc != crc.getValue()) { throw new ZipException( "invalid entry CRC (expected 0x" + Long.toHexString(e.crc) + " but got 0x" + Long.toHexString(crc.getValue()) + ")"); } } /* * Reads bytes, blocking until all bytes are read. */ private void readFully(byte[] b, int off, int len) throws IOException { while (len > 0) { int n = in.read(b, off, len); if (n == -1) { throw new EOFException(); } off += n; len -= n; } } /* * Fetches unsigned 16-bit value from byte array at specified offset. * The bytes are assumed to be in Intel (little-endian) byte order. */ private static final int get16(byte b[], int off) { return (b[off] & 0xff) | ((b[off+1] & 0xff) << 8); } /* * Fetches unsigned 32-bit value from byte array at specified offset. * The bytes are assumed to be in Intel (little-endian) byte order. */ private static final long get32(byte b[], int off) { return get16(b, off) | ((long)get16(b, off+2) << 16); } }
7. 在包中新建ZipOutputStream类,代码如下:
ZipOutputStream.java:
package cn.edu.xdian.crytoll; import java.io.IOException; import java.io.OutputStream; import java.util.HashSet; import java.util.Vector; import java.util.zip.CRC32; import java.util.zip.Deflater; import java.util.zip.ZipException; /** * 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 1.35, 07/31/06 */ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { private 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.method == DEFLATED && (entry.size == -1 || entry.csize == -1 || entry.crc == -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 static int version(ZipEntry e) throws ZipException { switch (e.method) { 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 ZipOutputStream(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.time == -1) { e.setTime(System.currentTimeMillis()); } if (e.method == -1) { e.method = method; // use default method } switch (e.method) { case DEFLATED: break; case STORED: // compressed size, uncompressed size, and crc-32 must all be // set for entries using STORED compression method if (e.size == -1) { e.size = e.csize; } else if (e.csize == -1) { e.csize = e.size; } else if (e.size != e.csize) { throw new ZipException( "STORED entry where compressed != uncompressed size"); } if (e.size == -1 || e.crc == -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.name)) { throw new ZipException("duplicate entry: " + e.name); } 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.method) { case DEFLATED: def.finish(); while (!def.finished()) { deflate(); } if ((current.flag & 8) == 0) { // verify size, compressed size, and crc-32 settings if (e.size != def.getBytesRead()) { throw new ZipException( "invalid entry size (expected " + e.size + " but got " + def.getBytesRead() + " bytes)"); } if (e.csize != def.getBytesWritten()) { throw new ZipException( "invalid entry compressed size (expected " + e.csize + " but got " + def.getBytesWritten() + " bytes)"); } if (e.crc != crc.getValue()) { throw new ZipException( "invalid entry CRC-32 (expected 0x" + Long.toHexString(e.crc) + " but got 0x" + Long.toHexString(crc.getValue()) + ")"); } } else { e.size = def.getBytesRead(); e.csize = def.getBytesWritten(); e.crc = crc.getValue(); writeEXT(e); } def.reset(); written += e.csize; break; case STORED: // we already know that both e.size and e.csize are the same if (e.size != written - locoff) { throw new ZipException( "invalid entry size (expected " + e.size + " but got " + (written - locoff) + " bytes)"); } if (e.crc != crc.getValue()) { throw new ZipException( "invalid entry crc-32 (expected 0x" + Long.toHexString(e.crc) + " 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.method) { case DEFLATED: super.write(b, off, len); break; case STORED: written += len; if (written - locoff > entry.size) { 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"); } // write central directory long off = written; for (XEntry xentry : xentries) writeCEN(xentry); writeEND(off, written - off); finished = true; } /** * 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 close() throws IOException { if (!closed) { 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.method); // compression method writeInt(e.time); // 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.crc); // crc-32 writeInt(e.csize); // compressed size writeInt(e.size); // uncompressed size } byte[] nameBytes = getUTF8Bytes(e.name); writeShort(nameBytes.length); writeShort(e.extra != null ? e.extra.length : 0); writeBytes(nameBytes, 0, nameBytes.length); if (e.extra != null) { writeBytes(e.extra, 0, e.extra.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.crc); // crc-32 writeInt(e.csize); // compressed size writeInt(e.size); // 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.method); // compression method writeInt(e.time); // last modification time writeInt(e.crc); // crc-32 writeInt(e.csize); // compressed size writeInt(e.size); // uncompressed size byte[] nameBytes = getUTF8Bytes(e.name); writeShort(nameBytes.length); writeShort(e.extra != null ? e.extra.length : 0); byte[] commentBytes; if (e.comment != null) { commentBytes = getUTF8Bytes(e.comment); 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.extra != null) { writeBytes(e.extra, 0, e.extra.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; } }
8. 新建一个Application Window,代码如下:
package cn.edu.xdian.crytoll; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JSpinner; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.border.EmptyBorder; import javax.swing.table.DefaultTableModel; import java.awt.GridBagLayout; import java.awt.GridBagConstraints; import java.awt.Insets; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import cn.edu.xdian.crytoll.ZipEntry; import cn.edu.xdian.crytoll.ZipInputStream; import cn.edu.xdian.crytoll.ZipOutputStream; /** * 获取文件列表的过滤器 * * @author 李钟尉 */ public class UnZipTextFileFrame extends JFrame { private JPanel contentPane; private JTextField forderField; private JTextField templetField; private File file; private File dir; private JTable table; private JTextField extNameField; private JSpinner startSpinner; private JTextField textField; private JTextField textField_1; private DefaultTableModel model; private String filesrc; /** * Launch the application. */ public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { try { UnZipTextFileFrame frame = new UnZipTextFileFrame(); frame.setVisible(true); } catch (Exception e) { e.printStackTrace(); } } }); } /** * Create the frame. */ public UnZipTextFileFrame() { setResizable(false); setTitle("压缩包解压到指定文件夹"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setBounds(100, 100, 500, 300); getContentPane().setLayout(null); textField = new JTextField(); textField.setBounds(10, 10, 158, 21); getContentPane().add(textField); textField.setColumns(10); JButton btnZip = new JButton("Zip\u6587\u4EF6"); btnZip.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { do_btnZip_actionPerformed(e); } }); btnZip.setBounds(178, 9, 93, 23); getContentPane().add(btnZip); textField_1 = new JTextField(); textField_1.setBounds(281, 10, 100, 21); getContentPane().add(textField_1); textField_1.setColumns(10); JButton btnNewButton = new JButton("解压到"); btnNewButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { do_btnNewButton_actionPerformed(e); } }); btnNewButton.setBounds(391, 9, 93, 23); getContentPane().add(btnNewButton); JScrollPane scrollPane = new JScrollPane(); scrollPane.setBounds(10, 41, 474, 170); getContentPane().add(scrollPane); table = new JTable(); scrollPane.setViewportView(table); model= (DefaultTableModel) table.getModel(); model.setColumnIdentifiers(new Object[] { "序号", "文件名"}); JButton button = new JButton("\u5F00\u59CB\u89E3\u538B\u7F29"); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { do_button_actionPerformed(e); } }); button.setBounds(178, 221, 100, 23); getContentPane().add(button); } protected void do_btnZip_actionPerformed(ActionEvent e){ JFileChooser chooser = new JFileChooser();// 创建文件选择器 int option = chooser.showOpenDialog(this);// 显示文件打开对话框 if (option == JFileChooser.APPROVE_OPTION) { file = chooser.getSelectedFile();// 获取选择的文件数组 filesrc=file.getAbsolutePath(); textField.setText(filesrc);// 清空文本框 } else { textField.setText("");// 清空文本框 } } protected void do_btnNewButton_actionPerformed(ActionEvent e){ JFileChooser chooser = new JFileChooser();// 创建文件选择器 // 设置选择器只针对文件夹生效 chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); int option = chooser.showOpenDialog(this);// 显示文件打开对话框 if (option == JFileChooser.APPROVE_OPTION) { dir = chooser.getSelectedFile();// 获取选择的文件夹 textField_1.setText(dir.toString());// 显示文件夹到文本框 } else { dir = null; textField_1.setText(""); } } protected void do_button_actionPerformed(ActionEvent e){ ZipInputStream zin; try{ //filesrc=new String(filesrc.getBytes("ISO-8859-1"),"UTF-8"); FileInputStream in = new FileInputStream(filesrc); zin=new ZipInputStream(in); ZipEntry entry; int i=1; while(((entry=zin.getNextEntry())!=null)&&!entry.isDirectory()){ File file=new File(dir.toString()+"/"+entry.getName()); if(!file.exists()){ file.createNewFile(); } zin.closeEntry(); Object[] property = new Object[2]; property[0] = i; property[1] = entry.getName(); model.addRow(property); i++; } }catch(Exception ex){ ex.printStackTrace(); } } }
效果如图: