--- old/src/java.base/share/classes/java/util/zip/ZipFile.java 2015-09-15 17:06:50.959582776 +0200 +++ new/src/java.base/share/classes/java/util/zip/ZipFile.java 2015-09-15 17:06:50.870582144 +0200 @@ -30,6 +30,9 @@ import java.io.IOException; import java.io.EOFException; import java.io.File; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayDeque; @@ -59,6 +62,9 @@ public class ZipFile implements ZipConstants, Closeable { private long jzfile; // address of jzfile data + private IntBuffer table; // direct buffer mapping jzfile->table + private ByteBuffer entries; // direct buffer mapping jzfile->entries + private int falsePositiveProbes; private final String name; // zip file name private final int total; // total number of entries private final boolean locsig; // if zip file starts with LOCSIG (usually true) @@ -89,6 +95,7 @@ private static native void initIDs(); private static final boolean usemmap; + private static final boolean useNativeTableProbe; static { // A system prpperty to disable mmap use to avoid vm crash when @@ -96,6 +103,12 @@ String prop = sun.misc.VM.getSavedProperty("sun.zip.disableMemoryMapping"); usemmap = (prop == null || !(prop.length() == 0 || prop.equalsIgnoreCase("true"))); + // A system property to enable mapping of native hash table of entries + // using direct byte buffers to facilitate a probe that speeds-up lookups + // for non-existent entries (important for class loading with large + // class-paths where JAR files are tried in order to find a class/resource) + prop = System.getProperty("sun.zip.useNativeTableProbe"); + useNativeTableProbe = prop != null && prop.equalsIgnoreCase("true"); } /** @@ -216,7 +229,12 @@ throw new NullPointerException("charset is null"); this.zc = ZipCoder.get(charset); long t0 = System.nanoTime(); - jzfile = open(name, mode, file.lastModified(), usemmap); + ByteBuffer[] tableAndEntries = useNativeTableProbe ? new ByteBuffer[2] : null; + jzfile = open(name, mode, file.lastModified(), usemmap, tableAndEntries); + if (tableAndEntries != null) { + table = tableAndEntries[0].order(ByteOrder.nativeOrder()).asIntBuffer(); + entries = tableAndEntries[1].order(ByteOrder.nativeOrder()); + } sun.misc.PerfCounter.getZipFileOpenTime().addElapsedTimeFrom(t0); sun.misc.PerfCounter.getZipFileCount().increment(); this.name = name; @@ -304,19 +322,83 @@ if (name == null) { throw new NullPointerException("name"); } - long jzentry = 0; synchronized (this) { ensureOpen(); - jzentry = getEntry(jzfile, zc.getBytes(name), true); - if (jzentry != 0) { - ZipEntry ze = getZipEntry(name, jzentry); - freeEntry(jzfile, jzentry); - return ze; + byte[] nameBytes = zc.getBytes(name); + int h = hash(nameBytes); + if (mayContainEntry(h) || + (name.charAt(name.length()-1) != '/' && mayContainEntry(31 * h + '/'))) { + long jzentry = getEntry(jzfile, nameBytes, true); + if (jzentry != 0) { + ZipEntry ze = getZipEntry(name, jzentry); + freeEntry(jzfile, jzentry); + return ze; + } else { + falsePositiveProbes++; + } } } return null; } + /** + * Lookup into a hash table maintained in two native arrays (table and entries) + * to probe whether there may be an entry present with specified 32 bit hash. + */ + private boolean mayContainEntry(int h) { + // when sun.zip.useNativeTableProbe is not defined, + // any entry hash may be valid... + if (table == null) return true; + + // use unsigned modulus to calculate index into hash table + int i = Integer.remainderUnsigned(h, table.capacity()); + // obtain index into entries array which holds a head of the hash table bucket + int head = table.get(i); + // special -1 value marks end of chain + while (head != -1) { + // + // entries array is composed of the following 16-byte entries (zip_util.h): + // + // typedef struct jzcell { + // unsigned int hash; /* 32 bit hashcode on name */ + // unsigned int next; /* hash chain: index into jzfile->entries */ + // jlong cenpos; /* Offset of central directory file header */ + // } jzcell; + // + int hi = head << 4; // index -> byte offset + if (entries.getInt(hi) == h) { // entries[head].hash + // got it + // TODO: return jzcell.cenpos to save native code from re-executing the same lookup + return true; + } + // advance to next in chain + head = entries.getInt(hi + 4); // entries[head].next + } + // no luck + return false; + } + + /** + * Equivalent to the following from zip_util.c: + *
+     *    static unsigned int
+     *                    hashN(const char *s, int length)
+     *    {
+     *        int h = 0;
+     *        while (length-- > 0)
+     *            h = 31*h + *s++;
+     *        return h;
+     *    }
+     * 
+ */ + private static int hash(byte[] bytes) { + int h = 0; + for (int i = 0; i < bytes.length; i++) { + h = 31 * h + ((int) bytes[i] & 0xFF); + } + return h; + } + private static native long getEntry(long jzfile, byte[] name, boolean addSlash); @@ -642,7 +724,8 @@ // Close the zip file long zf = this.jzfile; jzfile = 0; - + table = null; + entries = null; close(zf); } } @@ -799,7 +882,8 @@ } private static native long open(String name, int mode, long lastModified, - boolean usemmap) throws IOException; + boolean usemmap, + ByteBuffer[] tableAndEntries) throws IOException; private static native int getTotal(long jzfile); private static native boolean startsWithLOC(long jzfile); private static native int read(long jzfile, long jzentry,