Commit caaf8e96 authored by Phillip Webb's avatar Phillip Webb

Reduce churn when parsing Jar files

Update CentralDirectoryParser to reduce the number of objects created
when parsing the central directory. A single CentralDirectoryFileHeader
object is now reused as entries are parsed.

Fixes gh-5260
parent 5bc274ca
...@@ -113,7 +113,7 @@ final class AsciiBytes { ...@@ -113,7 +113,7 @@ final class AsciiBytes {
public AsciiBytes substring(int beginIndex, int endIndex) { public AsciiBytes substring(int beginIndex, int endIndex) {
int length = endIndex - beginIndex; int length = endIndex - beginIndex;
if (this.offset + length > this.length) { if (this.offset + length > this.bytes.length) {
throw new IndexOutOfBoundsException(); throw new IndexOutOfBoundsException();
} }
return new AsciiBytes(this.bytes, this.offset + beginIndex, length); return new AsciiBytes(this.bytes, this.offset + beginIndex, length);
......
...@@ -17,12 +17,10 @@ ...@@ -17,12 +17,10 @@
package org.springframework.boot.loader.jar; package org.springframework.boot.loader.jar;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar; import java.util.Calendar;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import org.springframework.boot.loader.data.RandomAccessData; import org.springframework.boot.loader.data.RandomAccessData;
import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess;
/** /**
* A ZIP File "Central directory file header record" (CDFH). * A ZIP File "Central directory file header record" (CDFH).
...@@ -36,26 +34,64 @@ final class CentralDirectoryFileHeader implements FileHeader { ...@@ -36,26 +34,64 @@ final class CentralDirectoryFileHeader implements FileHeader {
private static final AsciiBytes SLASH = new AsciiBytes("/"); private static final AsciiBytes SLASH = new AsciiBytes("/");
private final byte[] header; private static final byte[] NO_EXTRA = {};
private static final AsciiBytes NO_COMMENT = new AsciiBytes("");
private byte[] header;
private int headerOffset;
private AsciiBytes name; private AsciiBytes name;
private final byte[] extra; private byte[] extra;
private final AsciiBytes comment; private AsciiBytes comment;
private final long localHeaderOffset; private long localHeaderOffset;
CentralDirectoryFileHeader(byte[] header, InputStream inputStream) CentralDirectoryFileHeader() {
throws IOException { }
CentralDirectoryFileHeader(byte[] header, int headerOffset, AsciiBytes name,
byte[] extra, AsciiBytes comment, long localHeaderOffset) {
super();
this.header = header; this.header = header;
long nameLength = Bytes.littleEndianValue(header, 28, 2); this.headerOffset = headerOffset;
long extraLength = Bytes.littleEndianValue(header, 30, 2); this.name = name;
long commentLength = Bytes.littleEndianValue(header, 32, 2); this.extra = extra;
this.name = new AsciiBytes(Bytes.get(inputStream, nameLength)); this.comment = comment;
this.extra = Bytes.get(inputStream, extraLength); this.localHeaderOffset = localHeaderOffset;
this.comment = new AsciiBytes(Bytes.get(inputStream, commentLength)); }
this.localHeaderOffset = Bytes.littleEndianValue(header, 42, 4);
void load(byte[] data, int dataOffset, RandomAccessData variableData,
int variableOffset) throws IOException {
// Load fixed part
this.header = data;
this.headerOffset = dataOffset;
long nameLength = Bytes.littleEndianValue(data, dataOffset + 28, 2);
long extraLength = Bytes.littleEndianValue(data, dataOffset + 30, 2);
long commentLength = Bytes.littleEndianValue(data, dataOffset + 32, 2);
this.localHeaderOffset = Bytes.littleEndianValue(data, dataOffset + 42, 4);
// Load variable part
dataOffset += 46;
if (variableData != null) {
data = Bytes.get(variableData.getSubsection(variableOffset + 46,
nameLength + extraLength + commentLength));
dataOffset = 0;
}
this.name = new AsciiBytes(data, dataOffset, (int) nameLength);
this.extra = NO_EXTRA;
this.comment = NO_COMMENT;
if (extraLength > 0) {
this.extra = new byte[(int) extraLength];
System.arraycopy(data, (int) (dataOffset + nameLength), this.extra, 0,
this.extra.length);
}
if (commentLength > 0) {
this.comment = new AsciiBytes(data,
(int) (dataOffset + nameLength + extraLength), (int) commentLength);
}
} }
public AsciiBytes getName() { public AsciiBytes getName() {
...@@ -73,12 +109,12 @@ final class CentralDirectoryFileHeader implements FileHeader { ...@@ -73,12 +109,12 @@ final class CentralDirectoryFileHeader implements FileHeader {
@Override @Override
public int getMethod() { public int getMethod() {
return (int) Bytes.littleEndianValue(this.header, 10, 2); return (int) Bytes.littleEndianValue(this.header, this.headerOffset + 10, 2);
} }
public long getTime() { public long getTime() {
long date = Bytes.littleEndianValue(this.header, 14, 2); long date = Bytes.littleEndianValue(this.header, this.headerOffset + 14, 2);
long time = Bytes.littleEndianValue(this.header, 12, 2); long time = Bytes.littleEndianValue(this.header, this.headerOffset + 12, 2);
return decodeMsDosFormatDateTime(date, time).getTimeInMillis(); return decodeMsDosFormatDateTime(date, time).getTimeInMillis();
} }
...@@ -101,17 +137,17 @@ final class CentralDirectoryFileHeader implements FileHeader { ...@@ -101,17 +137,17 @@ final class CentralDirectoryFileHeader implements FileHeader {
} }
public long getCrc() { public long getCrc() {
return Bytes.littleEndianValue(this.header, 16, 4); return Bytes.littleEndianValue(this.header, this.headerOffset + 16, 4);
} }
@Override @Override
public long getCompressedSize() { public long getCompressedSize() {
return Bytes.littleEndianValue(this.header, 20, 4); return Bytes.littleEndianValue(this.header, this.headerOffset + 20, 4);
} }
@Override @Override
public long getSize() { public long getSize() {
return Bytes.littleEndianValue(this.header, 24, 4); return Bytes.littleEndianValue(this.header, this.headerOffset + 24, 4);
} }
public byte[] getExtra() { public byte[] getExtra() {
...@@ -127,32 +163,20 @@ final class CentralDirectoryFileHeader implements FileHeader { ...@@ -127,32 +163,20 @@ final class CentralDirectoryFileHeader implements FileHeader {
return this.localHeaderOffset; return this.localHeaderOffset;
} }
public static CentralDirectoryFileHeader fromRandomAccessData(RandomAccessData data, @Override
int offset) throws IOException { public CentralDirectoryFileHeader clone() {
InputStream inputStream = data.getSubsection(offset, data.getSize() - offset) byte[] header = new byte[46];
.getInputStream(ResourceAccess.ONCE); System.arraycopy(this.header, this.headerOffset, header, 0, header.length);
try { return new CentralDirectoryFileHeader(header, 0, this.name, header, this.comment,
return fromInputStream(inputStream); this.localHeaderOffset);
}
finally {
inputStream.close();
}
} }
/** public static CentralDirectoryFileHeader fromRandomAccessData(RandomAccessData data,
* Create a new {@link CentralDirectoryFileHeader} instance from the specified input int offset) throws IOException {
* stream. CentralDirectoryFileHeader fileHeader = new CentralDirectoryFileHeader();
* @param inputStream the input stream to load data from byte[] bytes = Bytes.get(data.getSubsection(offset, 46));
* @return a {@link CentralDirectoryFileHeader} or {@code null} fileHeader.load(bytes, 0, data, offset);
* @throws IOException in case of I/O errors return fileHeader;
*/
static CentralDirectoryFileHeader fromInputStream(InputStream inputStream)
throws IOException {
byte[] header = new byte[46];
if (!Bytes.fill(inputStream, header)) {
return null;
}
return new CentralDirectoryFileHeader(header, inputStream);
} }
} }
...@@ -17,12 +17,10 @@ ...@@ -17,12 +17,10 @@
package org.springframework.boot.loader.jar; package org.springframework.boot.loader.jar;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.springframework.boot.loader.data.RandomAccessData; import org.springframework.boot.loader.data.RandomAccessData;
import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess;
/** /**
* Parses the central directory from a JAR file. * Parses the central directory from a JAR file.
...@@ -56,26 +54,25 @@ class CentralDirectoryParser { ...@@ -56,26 +54,25 @@ class CentralDirectoryParser {
} }
RandomAccessData centralDirectoryData = endRecord.getCentralDirectory(data); RandomAccessData centralDirectoryData = endRecord.getCentralDirectory(data);
visitStart(endRecord, centralDirectoryData); visitStart(endRecord, centralDirectoryData);
InputStream inputStream = centralDirectoryData parseEntries(endRecord, centralDirectoryData);
.getInputStream(ResourceAccess.ONCE);
try {
int dataOffset = 0;
for (int i = 0; i < endRecord.getNumberOfRecords(); i++) {
CentralDirectoryFileHeader fileHeader = CentralDirectoryFileHeader
.fromInputStream(inputStream);
visitFileHeader(dataOffset, fileHeader);
dataOffset += this.CENTRAL_DIRECTORY_HEADER_BASE_SIZE
+ fileHeader.getName().length() + fileHeader.getComment().length()
+ fileHeader.getExtra().length;
}
}
finally {
inputStream.close();
}
visitEnd(); visitEnd();
return data; return data;
} }
private void parseEntries(CentralDirectoryEndRecord endRecord,
RandomAccessData centralDirectoryData) throws IOException {
byte[] bytes = Bytes.get(centralDirectoryData);
CentralDirectoryFileHeader fileHeader = new CentralDirectoryFileHeader();
int dataOffset = 0;
for (int i = 0; i < endRecord.getNumberOfRecords(); i++) {
fileHeader.load(bytes, dataOffset, null, 0);
visitFileHeader(dataOffset, fileHeader);
dataOffset += this.CENTRAL_DIRECTORY_HEADER_BASE_SIZE
+ fileHeader.getName().length() + fileHeader.getComment().length()
+ fileHeader.getExtra().length;
}
}
private RandomAccessData getArchiveData(CentralDirectoryEndRecord endRecord, private RandomAccessData getArchiveData(CentralDirectoryEndRecord endRecord,
RandomAccessData data) { RandomAccessData data) {
long offset = endRecord.getStartOfArchive(data); long offset = endRecord.getStartOfArchive(data);
......
...@@ -105,7 +105,7 @@ public class CentralDirectoryParserTests { ...@@ -105,7 +105,7 @@ public class CentralDirectoryParserTests {
@Override @Override
public void visitFileHeader(CentralDirectoryFileHeader fileHeader, public void visitFileHeader(CentralDirectoryFileHeader fileHeader,
int dataOffset) { int dataOffset) {
this.headers.add(fileHeader); this.headers.add(fileHeader.clone());
} }
@Override @Override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment