Skip to content

Commit caaf8e9

Browse files
committed
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 spring-projectsgh-5260
1 parent 5bc274c commit caaf8e9

File tree

4 files changed

+86
-65
lines changed

4 files changed

+86
-65
lines changed

spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/AsciiBytes.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public AsciiBytes substring(int beginIndex) {
113113

114114
public AsciiBytes substring(int beginIndex, int endIndex) {
115115
int length = endIndex - beginIndex;
116-
if (this.offset + length > this.length) {
116+
if (this.offset + length > this.bytes.length) {
117117
throw new IndexOutOfBoundsException();
118118
}
119119
return new AsciiBytes(this.bytes, this.offset + beginIndex, length);

spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryFileHeader.java

Lines changed: 69 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,10 @@
1717
package org.springframework.boot.loader.jar;
1818

1919
import java.io.IOException;
20-
import java.io.InputStream;
2120
import java.util.Calendar;
2221
import java.util.GregorianCalendar;
2322

2423
import org.springframework.boot.loader.data.RandomAccessData;
25-
import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess;
2624

2725
/**
2826
* A ZIP File "Central directory file header record" (CDFH).
@@ -36,26 +34,64 @@ final class CentralDirectoryFileHeader implements FileHeader {
3634

3735
private static final AsciiBytes SLASH = new AsciiBytes("/");
3836

39-
private final byte[] header;
37+
private static final byte[] NO_EXTRA = {};
38+
39+
private static final AsciiBytes NO_COMMENT = new AsciiBytes("");
40+
41+
private byte[] header;
42+
43+
private int headerOffset;
4044

4145
private AsciiBytes name;
4246

43-
private final byte[] extra;
47+
private byte[] extra;
4448

45-
private final AsciiBytes comment;
49+
private AsciiBytes comment;
4650

47-
private final long localHeaderOffset;
51+
private long localHeaderOffset;
4852

49-
CentralDirectoryFileHeader(byte[] header, InputStream inputStream)
50-
throws IOException {
53+
CentralDirectoryFileHeader() {
54+
}
55+
56+
CentralDirectoryFileHeader(byte[] header, int headerOffset, AsciiBytes name,
57+
byte[] extra, AsciiBytes comment, long localHeaderOffset) {
58+
super();
5159
this.header = header;
52-
long nameLength = Bytes.littleEndianValue(header, 28, 2);
53-
long extraLength = Bytes.littleEndianValue(header, 30, 2);
54-
long commentLength = Bytes.littleEndianValue(header, 32, 2);
55-
this.name = new AsciiBytes(Bytes.get(inputStream, nameLength));
56-
this.extra = Bytes.get(inputStream, extraLength);
57-
this.comment = new AsciiBytes(Bytes.get(inputStream, commentLength));
58-
this.localHeaderOffset = Bytes.littleEndianValue(header, 42, 4);
60+
this.headerOffset = headerOffset;
61+
this.name = name;
62+
this.extra = extra;
63+
this.comment = comment;
64+
this.localHeaderOffset = localHeaderOffset;
65+
}
66+
67+
void load(byte[] data, int dataOffset, RandomAccessData variableData,
68+
int variableOffset) throws IOException {
69+
// Load fixed part
70+
this.header = data;
71+
this.headerOffset = dataOffset;
72+
long nameLength = Bytes.littleEndianValue(data, dataOffset + 28, 2);
73+
long extraLength = Bytes.littleEndianValue(data, dataOffset + 30, 2);
74+
long commentLength = Bytes.littleEndianValue(data, dataOffset + 32, 2);
75+
this.localHeaderOffset = Bytes.littleEndianValue(data, dataOffset + 42, 4);
76+
// Load variable part
77+
dataOffset += 46;
78+
if (variableData != null) {
79+
data = Bytes.get(variableData.getSubsection(variableOffset + 46,
80+
nameLength + extraLength + commentLength));
81+
dataOffset = 0;
82+
}
83+
this.name = new AsciiBytes(data, dataOffset, (int) nameLength);
84+
this.extra = NO_EXTRA;
85+
this.comment = NO_COMMENT;
86+
if (extraLength > 0) {
87+
this.extra = new byte[(int) extraLength];
88+
System.arraycopy(data, (int) (dataOffset + nameLength), this.extra, 0,
89+
this.extra.length);
90+
}
91+
if (commentLength > 0) {
92+
this.comment = new AsciiBytes(data,
93+
(int) (dataOffset + nameLength + extraLength), (int) commentLength);
94+
}
5995
}
6096

6197
public AsciiBytes getName() {
@@ -73,12 +109,12 @@ public boolean isDirectory() {
73109

74110
@Override
75111
public int getMethod() {
76-
return (int) Bytes.littleEndianValue(this.header, 10, 2);
112+
return (int) Bytes.littleEndianValue(this.header, this.headerOffset + 10, 2);
77113
}
78114

79115
public long getTime() {
80-
long date = Bytes.littleEndianValue(this.header, 14, 2);
81-
long time = Bytes.littleEndianValue(this.header, 12, 2);
116+
long date = Bytes.littleEndianValue(this.header, this.headerOffset + 14, 2);
117+
long time = Bytes.littleEndianValue(this.header, this.headerOffset + 12, 2);
82118
return decodeMsDosFormatDateTime(date, time).getTimeInMillis();
83119
}
84120

@@ -101,17 +137,17 @@ private Calendar decodeMsDosFormatDateTime(long date, long time) {
101137
}
102138

103139
public long getCrc() {
104-
return Bytes.littleEndianValue(this.header, 16, 4);
140+
return Bytes.littleEndianValue(this.header, this.headerOffset + 16, 4);
105141
}
106142

107143
@Override
108144
public long getCompressedSize() {
109-
return Bytes.littleEndianValue(this.header, 20, 4);
145+
return Bytes.littleEndianValue(this.header, this.headerOffset + 20, 4);
110146
}
111147

112148
@Override
113149
public long getSize() {
114-
return Bytes.littleEndianValue(this.header, 24, 4);
150+
return Bytes.littleEndianValue(this.header, this.headerOffset + 24, 4);
115151
}
116152

117153
public byte[] getExtra() {
@@ -127,32 +163,20 @@ public long getLocalHeaderOffset() {
127163
return this.localHeaderOffset;
128164
}
129165

130-
public static CentralDirectoryFileHeader fromRandomAccessData(RandomAccessData data,
131-
int offset) throws IOException {
132-
InputStream inputStream = data.getSubsection(offset, data.getSize() - offset)
133-
.getInputStream(ResourceAccess.ONCE);
134-
try {
135-
return fromInputStream(inputStream);
136-
}
137-
finally {
138-
inputStream.close();
139-
}
166+
@Override
167+
public CentralDirectoryFileHeader clone() {
168+
byte[] header = new byte[46];
169+
System.arraycopy(this.header, this.headerOffset, header, 0, header.length);
170+
return new CentralDirectoryFileHeader(header, 0, this.name, header, this.comment,
171+
this.localHeaderOffset);
140172
}
141173

142-
/**
143-
* Create a new {@link CentralDirectoryFileHeader} instance from the specified input
144-
* stream.
145-
* @param inputStream the input stream to load data from
146-
* @return a {@link CentralDirectoryFileHeader} or {@code null}
147-
* @throws IOException in case of I/O errors
148-
*/
149-
static CentralDirectoryFileHeader fromInputStream(InputStream inputStream)
150-
throws IOException {
151-
byte[] header = new byte[46];
152-
if (!Bytes.fill(inputStream, header)) {
153-
return null;
154-
}
155-
return new CentralDirectoryFileHeader(header, inputStream);
174+
public static CentralDirectoryFileHeader fromRandomAccessData(RandomAccessData data,
175+
int offset) throws IOException {
176+
CentralDirectoryFileHeader fileHeader = new CentralDirectoryFileHeader();
177+
byte[] bytes = Bytes.get(data.getSubsection(offset, 46));
178+
fileHeader.load(bytes, 0, data, offset);
179+
return fileHeader;
156180
}
157181

158182
}

spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryParser.java

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,10 @@
1717
package org.springframework.boot.loader.jar;
1818

1919
import java.io.IOException;
20-
import java.io.InputStream;
2120
import java.util.ArrayList;
2221
import java.util.List;
2322

2423
import org.springframework.boot.loader.data.RandomAccessData;
25-
import org.springframework.boot.loader.data.RandomAccessData.ResourceAccess;
2624

2725
/**
2826
* Parses the central directory from a JAR file.
@@ -56,26 +54,25 @@ public RandomAccessData parse(RandomAccessData data, boolean skipPrefixBytes)
5654
}
5755
RandomAccessData centralDirectoryData = endRecord.getCentralDirectory(data);
5856
visitStart(endRecord, centralDirectoryData);
59-
InputStream inputStream = centralDirectoryData
60-
.getInputStream(ResourceAccess.ONCE);
61-
try {
62-
int dataOffset = 0;
63-
for (int i = 0; i < endRecord.getNumberOfRecords(); i++) {
64-
CentralDirectoryFileHeader fileHeader = CentralDirectoryFileHeader
65-
.fromInputStream(inputStream);
66-
visitFileHeader(dataOffset, fileHeader);
67-
dataOffset += this.CENTRAL_DIRECTORY_HEADER_BASE_SIZE
68-
+ fileHeader.getName().length() + fileHeader.getComment().length()
69-
+ fileHeader.getExtra().length;
70-
}
71-
}
72-
finally {
73-
inputStream.close();
74-
}
57+
parseEntries(endRecord, centralDirectoryData);
7558
visitEnd();
7659
return data;
7760
}
7861

62+
private void parseEntries(CentralDirectoryEndRecord endRecord,
63+
RandomAccessData centralDirectoryData) throws IOException {
64+
byte[] bytes = Bytes.get(centralDirectoryData);
65+
CentralDirectoryFileHeader fileHeader = new CentralDirectoryFileHeader();
66+
int dataOffset = 0;
67+
for (int i = 0; i < endRecord.getNumberOfRecords(); i++) {
68+
fileHeader.load(bytes, dataOffset, null, 0);
69+
visitFileHeader(dataOffset, fileHeader);
70+
dataOffset += this.CENTRAL_DIRECTORY_HEADER_BASE_SIZE
71+
+ fileHeader.getName().length() + fileHeader.getComment().length()
72+
+ fileHeader.getExtra().length;
73+
}
74+
}
75+
7976
private RandomAccessData getArchiveData(CentralDirectoryEndRecord endRecord,
8077
RandomAccessData data) {
8178
long offset = endRecord.getStartOfArchive(data);

spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/CentralDirectoryParserTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public void visitStart(CentralDirectoryEndRecord endRecord,
105105
@Override
106106
public void visitFileHeader(CentralDirectoryFileHeader fileHeader,
107107
int dataOffset) {
108-
this.headers.add(fileHeader);
108+
this.headers.add(fileHeader.clone());
109109
}
110110

111111
@Override

0 commit comments

Comments
 (0)