8242596: Improve JarFile.getEntry performance for multi-release jar files

Co-authored-by: Eirik Bjørsnøs <eirbjo@gmail.com>
Reviewed-by: lancea, redestad
This commit is contained in:
Claes Redestad 2020-04-17 11:46:59 +02:00
parent 07156dd25b
commit aeb2f9492e
3 changed files with 123 additions and 30 deletions

View file

@ -431,10 +431,6 @@ public class JarFile extends ZipFile {
return man;
}
private String[] getMetaInfEntryNames() {
return JUZFA.getMetaInfEntryNames((ZipFile)this);
}
/**
* Returns the {@code JarEntry} for the given base entry name or
* {@code null} if not found.
@ -504,11 +500,15 @@ public class JarFile extends ZipFile {
* </div>
*/
public ZipEntry getEntry(String name) {
JarFileEntry je = getEntry0(name);
if (isMultiRelease()) {
return getVersionedEntry(name, je);
JarEntry je = getVersionedEntry(name, null);
if (je == null) {
je = getEntry0(name);
}
return je;
} else {
return getEntry0(name);
}
return je;
}
/**
@ -598,21 +598,29 @@ public class JarFile extends ZipFile {
return name;
}
private JarEntry getVersionedEntry(String name, JarEntry je) {
if (BASE_VERSION_FEATURE < versionFeature) {
if (!name.startsWith(META_INF)) {
private JarEntry getVersionedEntry(String name, JarEntry defaultEntry) {
if (!name.startsWith(META_INF)) {
int[] versions = JUZFA.getMetaInfVersions(this);
if (BASE_VERSION_FEATURE < versionFeature && versions.length > 0) {
// search for versioned entry
int v = versionFeature;
while (v > BASE_VERSION_FEATURE) {
JarFileEntry vje = getEntry0(META_INF_VERSIONS + v + "/" + name);
for (int i = versions.length - 1; i >= 0; i--) {
int version = versions[i];
// skip versions above versionFeature
if (version > versionFeature) {
continue;
}
// skip versions below base version
if (version < BASE_VERSION_FEATURE) {
break;
}
JarFileEntry vje = getEntry0(META_INF_VERSIONS + version + "/" + name);
if (vje != null) {
return vje.withBasename(name);
}
v--;
}
}
}
return je;
return defaultEntry;
}
// placeholder for now
@ -707,7 +715,7 @@ public class JarFile extends ZipFile {
}
if (verify) {
String[] names = getMetaInfEntryNames();
String[] names = JUZFA.getMetaInfEntryNames(this);
if (names != null) {
for (String nameLower : names) {
String name = nameLower.toUpperCase(Locale.ENGLISH);
@ -738,7 +746,7 @@ public class JarFile extends ZipFile {
// Verify "META-INF/" entries...
try {
String[] names = getMetaInfEntryNames();
String[] names = JUZFA.getMetaInfEntryNames(this);
if (names != null) {
for (String name : names) {
String uname = name.toUpperCase(Locale.ENGLISH);
@ -932,7 +940,7 @@ public class JarFile extends ZipFile {
if (manEntry == null) {
// If not found, then iterate through all the "META-INF/"
// entries to find a match.
String[] names = getMetaInfEntryNames();
String[] names = JUZFA.getMetaInfEntryNames(this);
if (names != null) {
for (String name : names) {
if (MANIFEST_NAME.equals(name.toUpperCase(Locale.ENGLISH))) {
@ -1016,7 +1024,7 @@ public class JarFile extends ZipFile {
byte[] lbuf = new byte[512];
Attributes attr = new Attributes();
attr.read(new Manifest.FastInputStream(
new ByteArrayInputStream(b)), lbuf);
new ByteArrayInputStream(b)), lbuf);
isMultiRelease = Boolean.parseBoolean(
attr.getValue(Attributes.Name.MULTI_RELEASE));
}
@ -1068,7 +1076,7 @@ public class JarFile extends ZipFile {
*/
JarEntry newEntry(String name) {
if (isMultiRelease()) {
JarEntry vje = getVersionedEntry(name, (JarEntry)null);
JarEntry vje = getVersionedEntry(name, null);
if (vje != null) {
return vje;
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 1995, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1995, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -50,14 +50,15 @@ import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.TreeSet;
import java.util.WeakHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.JavaUtilZipFileAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.misc.VM;
@ -1048,8 +1049,21 @@ public class ZipFile implements ZipConstants, Closeable {
}
}
/**
* Returns the versions for which there exists a non-directory
* entry that begin with "META-INF/versions/" (case ignored).
* This method is used in JarFile, via SharedSecrets, as an
* optimization when looking up potentially versioned entries.
* Returns an empty array if no versioned entries exist.
*/
private int[] getMetaInfVersions() {
synchronized (this) {
ensureOpen();
return res.zsrc.metaVersions;
}
}
private static boolean isWindows;
private static final JavaLangAccess JLA;
static {
SharedSecrets.setJavaUtilZipFileAccess(
@ -1059,8 +1073,12 @@ public class ZipFile implements ZipConstants, Closeable {
return zip.res.zsrc.startsWithLoc;
}
@Override
public String[] getMetaInfEntryNames(ZipFile zip) {
return zip.getMetaInfEntryNames();
public String[] getMetaInfEntryNames(JarFile jar) {
return ((ZipFile)jar).getMetaInfEntryNames();
}
@Override
public int[] getMetaInfVersions(JarFile jar) {
return ((ZipFile)jar).getMetaInfVersions();
}
@Override
public JarEntry getEntry(ZipFile zip, String name,
@ -1083,11 +1101,14 @@ public class ZipFile implements ZipConstants, Closeable {
}
}
);
JLA = SharedSecrets.getJavaLangAccess();
isWindows = VM.getSavedProperty("os.name").contains("Windows");
}
private static class Source {
// "META-INF/".length()
private static final int META_INF_LENGTH = 9;
private static final int[] EMPTY_META_VERSIONS = new int[0];
private final Key key; // the key in files
private int refs = 1;
@ -1097,6 +1118,7 @@ public class ZipFile implements ZipConstants, Closeable {
private byte[] comment; // zip file comment
// list of meta entries in META-INF dir
private int[] metanames;
private int[] metaVersions; // list of unique versions found in META-INF/versions/
private final boolean startsWithLoc; // true, if zip file starts with LOCSIG (usually true)
// A Hashmap for all entries.
@ -1237,6 +1259,7 @@ public class ZipFile implements ZipConstants, Closeable {
entries = null;
table = null;
metanames = null;
metaVersions = EMPTY_META_VERSIONS;
}
private static final int BUF_SIZE = 8192;
@ -1424,6 +1447,8 @@ public class ZipFile implements ZipConstants, Closeable {
// list for all meta entries
ArrayList<Integer> metanamesList = null;
// Set of all version numbers seen in META-INF/versions/
Set<Integer> metaVersionsSet = null;
// Iterate through the entries in the central directory
int i = 0;
@ -1461,6 +1486,17 @@ public class ZipFile implements ZipConstants, Closeable {
if (metanamesList == null)
metanamesList = new ArrayList<>(4);
metanamesList.add(pos);
// If this is a versioned entry, parse the version
// and store it for later. This optimizes lookup
// performance in multi-release jar files
int version = getMetaVersion(cen,
pos + CENHDR + META_INF_LENGTH, nlen - META_INF_LENGTH);
if (version > 0) {
if (metaVersionsSet == null)
metaVersionsSet = new TreeSet<>();
metaVersionsSet.add(version);
}
}
// skip ext and comment
pos += (CENHDR + nlen + elen + clen);
@ -1473,6 +1509,15 @@ public class ZipFile implements ZipConstants, Closeable {
metanames[j] = metanamesList.get(j);
}
}
if (metaVersionsSet != null) {
metaVersions = new int[metaVersionsSet.size()];
int c = 0;
for (Integer version : metaVersionsSet) {
metaVersions[c++] = version;
}
} else {
metaVersions = EMPTY_META_VERSIONS;
}
if (pos + ENDHDR != cen.length) {
zerror("invalid CEN header (bad header size)");
}
@ -1550,7 +1595,7 @@ public class ZipFile implements ZipConstants, Closeable {
*/
private static boolean isMetaName(byte[] name, int off, int len) {
// Use the "oldest ASCII trick in the book"
return len > 9 // "META-INF/".length()
return len > META_INF_LENGTH // "META-INF/".length()
&& name[off + len - 1] != '/' // non-directory
&& (name[off++] | 0x20) == 'm'
&& (name[off++] | 0x20) == 'e'
@ -1563,6 +1608,45 @@ public class ZipFile implements ZipConstants, Closeable {
&& (name[off] ) == '/';
}
/*
* If the bytes represents a non-directory name beginning
* with "versions/", continuing with a positive integer,
* followed by a '/', then return that integer value.
* Otherwise, return 0
*/
private static int getMetaVersion(byte[] name, int off, int len) {
int nend = off + len;
if (!(len > 10 // "versions//".length()
&& name[off + len - 1] != '/' // non-directory
&& (name[off++] | 0x20) == 'v'
&& (name[off++] | 0x20) == 'e'
&& (name[off++] | 0x20) == 'r'
&& (name[off++] | 0x20) == 's'
&& (name[off++] | 0x20) == 'i'
&& (name[off++] | 0x20) == 'o'
&& (name[off++] | 0x20) == 'n'
&& (name[off++] | 0x20) == 's'
&& (name[off++] ) == '/')) {
return 0;
}
int version = 0;
while (off < nend) {
final byte c = name[off++];
if (c == '/') {
return version;
}
if (c < '0' || c > '9') {
return 0;
}
version = version * 10 + c - '0';
// Check for overflow and leading zeros
if (version <= 0) {
return 0;
}
}
return 0;
}
/**
* Returns the number of CEN headers in a central directory.
* Will not throw, even if the zip file is corrupt.

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,16 +25,17 @@
package jdk.internal.access;
import java.io.IOException;
import java.util.Enumeration;
import java.util.function.Function;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Stream;
import java.util.zip.ZipFile;
public interface JavaUtilZipFileAccess {
public boolean startsWithLocHeader(ZipFile zip);
public String[] getMetaInfEntryNames(ZipFile zip);
public String[] getMetaInfEntryNames(JarFile zip);
public int[] getMetaInfVersions(JarFile zip);
public JarEntry getEntry(ZipFile zip, String name, Function<String, JarEntry> func);
public Enumeration<JarEntry> entries(ZipFile zip, Function<String, JarEntry> func);
public Stream<JarEntry> stream(ZipFile zip, Function<String, JarEntry> func);