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 {
public AsciiBytes substring(int beginIndex, int endIndex) {
int length = endIndex - beginIndex;
if (this.offset + length > this.length) {
if (this.offset + length > this.bytes.length) {
throw new IndexOutOfBoundsException();
}
return new AsciiBytes(this.bytes, this.offset + beginIndex, length);
......
......@@ -17,12 +17,10 @@
package org.springframework.boot.loader.jar;
import java.io.IOException;
import java.io.InputStream;
import java.util.Calendar;
import java.util.GregorianCalendar;
import org.springframework.boot.loader.data.RandomAccessData;
import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess;
/**
* A ZIP File "Central directory file header record" (CDFH).
......@@ -36,26 +34,64 @@ final class CentralDirectoryFileHeader implements FileHeader {
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 final byte[] extra;
private byte[] extra;
private final AsciiBytes comment;
private AsciiBytes comment;
private final long localHeaderOffset;
private long localHeaderOffset;
CentralDirectoryFileHeader() {
}
CentralDirectoryFileHeader(byte[] header, InputStream inputStream)
throws IOException {
CentralDirectoryFileHeader(byte[] header, int headerOffset, AsciiBytes name,
byte[] extra, AsciiBytes comment, long localHeaderOffset) {
super();
this.header = header;
long nameLength = Bytes.littleEndianValue(header, 28, 2);
long extraLength = Bytes.littleEndianValue(header, 30, 2);
long commentLength = Bytes.littleEndianValue(header, 32, 2);
this.name = new AsciiBytes(Bytes.get(inputStream, nameLength));
this.extra = Bytes.get(inputStream, extraLength);
this.comment = new AsciiBytes(Bytes.get(inputStream, commentLength));
this.localHeaderOffset = Bytes.littleEndianValue(header, 42, 4);
this.headerOffset = headerOffset;
this.name = name;
this.extra = extra;
this.comment = comment;
this.localHeaderOffset = localHeaderOffset;
}
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() {
......@@ -73,12 +109,12 @@ final class CentralDirectoryFileHeader implements FileHeader {
@Override
public int getMethod() {
return (int) Bytes.littleEndianValue(this.header, 10, 2);
return (int) Bytes.littleEndianValue(this.header, this.headerOffset + 10, 2);
}
public long getTime() {
long date = Bytes.littleEndianValue(this.header, 14, 2);
long time = Bytes.littleEndianValue(this.header, 12, 2);
long date = Bytes.littleEndianValue(this.header, this.headerOffset + 14, 2);
long time = Bytes.littleEndianValue(this.header, this.headerOffset + 12, 2);
return decodeMsDosFormatDateTime(date, time).getTimeInMillis();
}
......@@ -101,17 +137,17 @@ final class CentralDirectoryFileHeader implements FileHeader {
}
public long getCrc() {
return Bytes.littleEndianValue(this.header, 16, 4);
return Bytes.littleEndianValue(this.header, this.headerOffset + 16, 4);
}
@Override
public long getCompressedSize() {
return Bytes.littleEndianValue(this.header, 20, 4);
return Bytes.littleEndianValue(this.header, this.headerOffset + 20, 4);
}
@Override
public long getSize() {
return Bytes.littleEndianValue(this.header, 24, 4);
return Bytes.littleEndianValue(this.header, this.headerOffset + 24, 4);
}
public byte[] getExtra() {
......@@ -127,32 +163,20 @@ final class CentralDirectoryFileHeader implements FileHeader {
return this.localHeaderOffset;
}
public static CentralDirectoryFileHeader fromRandomAccessData(RandomAccessData data,
int offset) throws IOException {
InputStream inputStream = data.getSubsection(offset, data.getSize() - offset)
.getInputStream(ResourceAccess.ONCE);
try {
return fromInputStream(inputStream);
}
finally {
inputStream.close();
}
}
/**
* Create a new {@link CentralDirectoryFileHeader} instance from the specified input
* stream.
* @param inputStream the input stream to load data from
* @return a {@link CentralDirectoryFileHeader} or {@code null}
* @throws IOException in case of I/O errors
*/
static CentralDirectoryFileHeader fromInputStream(InputStream inputStream)
throws IOException {
@Override
public CentralDirectoryFileHeader clone() {
byte[] header = new byte[46];
if (!Bytes.fill(inputStream, header)) {
return null;
System.arraycopy(this.header, this.headerOffset, header, 0, header.length);
return new CentralDirectoryFileHeader(header, 0, this.name, header, this.comment,
this.localHeaderOffset);
}
return new CentralDirectoryFileHeader(header, inputStream);
public static CentralDirectoryFileHeader fromRandomAccessData(RandomAccessData data,
int offset) throws IOException {
CentralDirectoryFileHeader fileHeader = new CentralDirectoryFileHeader();
byte[] bytes = Bytes.get(data.getSubsection(offset, 46));
fileHeader.load(bytes, 0, data, offset);
return fileHeader;
}
}
......@@ -17,12 +17,10 @@
package org.springframework.boot.loader.jar;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.loader.data.RandomAccessData;
import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess;
/**
* Parses the central directory from a JAR file.
......@@ -56,25 +54,24 @@ class CentralDirectoryParser {
}
RandomAccessData centralDirectoryData = endRecord.getCentralDirectory(data);
visitStart(endRecord, centralDirectoryData);
InputStream inputStream = centralDirectoryData
.getInputStream(ResourceAccess.ONCE);
try {
parseEntries(endRecord, centralDirectoryData);
visitEnd();
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++) {
CentralDirectoryFileHeader fileHeader = CentralDirectoryFileHeader
.fromInputStream(inputStream);
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;
}
}
finally {
inputStream.close();
}
visitEnd();
return data;
}
private RandomAccessData getArchiveData(CentralDirectoryEndRecord endRecord,
RandomAccessData data) {
......
......@@ -105,7 +105,7 @@ public class CentralDirectoryParserTests {
@Override
public void visitFileHeader(CentralDirectoryFileHeader fileHeader,
int dataOffset) {
this.headers.add(fileHeader);
this.headers.add(fileHeader.clone());
}
@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