src/share/classes/java/util/zip/ZipFile.java
Print this page
@@ -29,15 +29,17 @@
import java.io.InputStream;
import java.io.IOException;
import java.io.EOFException;
import java.io.File;
import java.nio.charset.Charset;
-import java.util.Vector;
+import java.util.ArrayDeque;
+import java.util.Deque;
import java.util.Enumeration;
-import java.util.Set;
-import java.util.HashSet;
+import java.util.HashMap;
+import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.WeakHashMap;
import java.security.AccessController;
import sun.security.action.GetPropertyAction;
import static java.util.zip.ZipConstants64.*;
/**
@@ -52,11 +54,11 @@
public
class ZipFile implements ZipConstants, Closeable {
private long jzfile; // address of jzfile data
private String name; // zip file name
private int total; // total number of entries
- private boolean closeRequested;
+ private volatile boolean closeRequested = false;
private static final int STORED = ZipEntry.STORED;
private static final int DEFLATED = ZipEntry.DEFLATED;
/**
@@ -312,12 +314,13 @@
boolean addSlash);
// freeEntry releases the C jzentry struct.
private static native void freeEntry(long jzfile, long jzentry);
- // the outstanding inputstreams that need to be closed.
- private Set<InputStream> streams = new HashSet<>();
+ // the outstanding inputstreams that need to be closed,
+ // mapped to the inflater objects they use.
+ private final Map<InputStream, Inflater> streams = new WeakHashMap<>();
/**
* Returns an input stream for reading the contents of the specified
* zip file entry.
*
@@ -349,92 +352,117 @@
}
in = new ZipFileInputStream(jzentry);
switch (getEntryMethod(jzentry)) {
case STORED:
- streams.add(in);
+ synchronized (streams) {
+ streams.put(in, null);
+ }
return in;
case DEFLATED:
- final ZipFileInputStream zfin = in;
// MORE: Compute good size for inflater stream:
long size = getEntrySize(jzentry) + 2; // Inflater likes a bit of slack
if (size > 65536) size = 8192;
if (size <= 0) size = 4096;
- InputStream is = new InflaterInputStream(zfin, getInflater(), (int)size) {
- private boolean isClosed = false;
+ Inflater inf = getInflater();
+ InputStream is =
+ new ZipFileInflaterInputStream(in, inf, (int)size);
+ synchronized (streams) {
+ streams.put(is, inf);
+ }
+ return is;
+ default:
+ throw new ZipException("invalid compression method");
+ }
+ }
+ }
+
+ private class ZipFileInflaterInputStream extends InflaterInputStream {
+ private volatile boolean closeRequested = false;
+ private boolean eof = false;
+ private final ZipFileInputStream zfin;
+
+ ZipFileInflaterInputStream(ZipFileInputStream zfin, Inflater inf,
+ int size) {
+ super(zfin, inf, size);
+ this.zfin = zfin;
+ }
public void close() throws IOException {
- if (!isClosed) {
+ if (closeRequested)
+ return;
+ closeRequested = true;
+
super.close();
+ Inflater inf;
+ synchronized (streams) {
+ inf = streams.remove(this);
+ }
+ if (inf != null) {
releaseInflater(inf);
- isClosed = true;
}
}
+
// Override fill() method to provide an extra "dummy" byte
// at the end of the input stream. This is required when
// using the "nowrap" Inflater option.
protected void fill() throws IOException {
if (eof) {
- throw new EOFException(
- "Unexpected end of ZLIB input stream");
+ throw new EOFException("Unexpected end of ZLIB input stream");
}
- len = this.in.read(buf, 0, buf.length);
+ len = in.read(buf, 0, buf.length);
if (len == -1) {
buf[0] = 0;
len = 1;
eof = true;
}
inf.setInput(buf, 0, len);
}
- private boolean eof;
public int available() throws IOException {
- if (isClosed)
+ if (closeRequested)
return 0;
long avail = zfin.size() - inf.getBytesWritten();
- return avail > (long) Integer.MAX_VALUE ?
- Integer.MAX_VALUE : (int) avail;
- }
- };
- streams.add(is);
- return is;
- default:
- throw new ZipException("invalid compression method");
+ return (avail > (long) Integer.MAX_VALUE ?
+ Integer.MAX_VALUE : (int) avail);
}
+
+ protected void finalize() throws Throwable {
+ close();
}
}
/*
* Gets an inflater from the list of available inflaters or allocates
* a new one.
*/
private Inflater getInflater() {
- synchronized (inflaters) {
- int size = inflaters.size();
- if (size > 0) {
- Inflater inf = (Inflater)inflaters.remove(size - 1);
+ Inflater inf;
+ synchronized (inflaterCache) {
+ while (null != (inf = inflaterCache.poll())) {
+ if (false == inf.ended()) {
return inf;
- } else {
- return new Inflater(true);
}
}
}
+ return new Inflater(true);
+ }
/*
* Releases the specified inflater to the list of available inflaters.
*/
private void releaseInflater(Inflater inf) {
- synchronized (inflaters) {
- if (inf.ended())
- return;
+ if (false == inf.ended()) {
inf.reset();
- inflaters.add(inf);
+ synchronized (inflaterCache) {
+ inflaterCache.add(inf);
+ }
}
}
// List of available Inflater objects for decompression
- private Vector inflaters = new Vector();
+ private Deque<Inflater> inflaterCache = new ArrayDeque<>();
/**
* Returns the path name of the ZIP file.
* @return the path name of the ZIP file
*/
@@ -538,43 +566,51 @@
* getInputStream} method.
*
* @throws IOException if an I/O error has occurred
*/
public void close() throws IOException {
- synchronized (this) {
+ if (closeRequested)
+ return;
closeRequested = true;
- if (streams.size() !=0) {
- Set<InputStream> copy = streams;
- streams = new HashSet<>();
- for (InputStream is: copy)
- is.close();
+ synchronized (this) {
+ // Close streams, release their inflaters
+ synchronized (streams) {
+ if (false == streams.isEmpty()) {
+ Map<InputStream, Inflater> copy = new HashMap<>(streams);
+ streams.clear();
+ for (Map.Entry<InputStream, Inflater> e : copy.entrySet()) {
+ e.getKey().close();
+ Inflater inf = e.getValue();
+ if (inf != null) {
+ inf.end();
+ }
+ }
+ }
+ }
+
+ // Release cached inflaters
+ Inflater inf;
+ synchronized (inflaterCache) {
+ while (null != (inf = inflaterCache.poll())) {
+ inf.end();
+ }
}
if (jzfile != 0) {
// Close the zip file
long zf = this.jzfile;
jzfile = 0;
close(zf);
-
- // Release inflaters
- synchronized (inflaters) {
- int size = inflaters.size();
- for (int i = 0; i < size; i++) {
- Inflater inf = (Inflater)inflaters.get(i);
- inf.end();
- }
}
}
}
- }
-
/**
- * Ensures that the <code>close</code> method of this ZIP file is
- * called when there are no more references to it.
+ * Ensures that the system resources held by this ZipFile object are
+ * released when there are no more references to it.
*
* <p>
* Since the time when GC would invoke this method is undetermined,
* it is strongly recommended that applications invoke the <code>close</code>
* method as soon they have finished accessing this <code>ZipFile</code>.
@@ -609,10 +645,11 @@
/*
* Inner class implementing the input stream used to read a
* (possibly compressed) zip file entry.
*/
private class ZipFileInputStream extends InputStream {
+ private volatile boolean closeRequested = false;
protected long jzentry; // address of jzentry data
private long pos; // current position within entry data
protected long rem; // number of remaining bytes within entry
protected long size; // uncompressed size of this entry
@@ -676,19 +713,29 @@
public long size() {
return size;
}
public void close() {
+ if (closeRequested)
+ return;
+ closeRequested = true;
+
rem = 0;
synchronized (ZipFile.this) {
if (jzentry != 0 && ZipFile.this.jzfile != 0) {
freeEntry(ZipFile.this.jzfile, jzentry);
jzentry = 0;
}
+ }
+ synchronized (streams) {
streams.remove(this);
}
}
+
+ protected void finalize() {
+ close();
+ }
}
private static native long open(String name, int mode, long lastModified,
boolean usemmap) throws IOException;