Use a map to look up layer index child nodes

LayersIndex.Node kept its children in a list and scanned that list
linearly to find the child for each path segment. Building the index is
therefore O(entries x siblings), a cost that is dominated by the largest
flat directory in the jar: BOOT-INF/lib/ for the dependencies, and any
bundled resource directory such as a front-end build output.

Keep the children in a LinkedHashMap keyed by the segment name so that
lookups are constant time. Insertion order is preserved, so the order in
which buildIndex() walks the tree is unchanged.

For a jar with 32,529 entries, 426 dependencies and a 12,000 file static
resource directory, this reduces the number of string comparisons from
73.7M to 222K and the time spent building the index from 197ms to 12ms.
For a jar with no large flat directory (20,529 entries, 426
dependencies) the gain is much smaller: 1.58M comparisons to 162K, and
12.5ms to 9.6ms. The generated layers.idx is byte for byte identical in
both cases.

See gh-51654

Signed-off-by: Junggi Kim <kimjg2477@gmail.com>
This commit is contained in:
Junggi Kim
2026-09-10 16:30:35 +02:00
committed by Stéphane Nicoll
parent c966eca3e7
commit 823744c0b2
@@ -21,11 +21,12 @@ import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.util.LinkedMultiValueMap;
@@ -47,6 +48,7 @@ import org.springframework.util.MultiValueMap;
* @author Madhura Bhave
* @author Andy Wilkinson
* @author Phillip Webb
* @author Junggi Kim
* @since 2.3.0
*/
public class LayersIndex {
@@ -116,28 +118,21 @@ public class LayersIndex {
private final Set<Layer> layers;
private final List<Node> children = new ArrayList<>();
private final Map<String, Node> children = new LinkedHashMap<>();
Node() {
this.name = "";
this.layers = new HashSet<>();
this("");
}
Node(String name, Layer layer) {
Node(String name) {
this.name = name;
this.layers = new HashSet<>(Collections.singleton(layer));
this.layers = new HashSet<>();
}
Node updateOrAddNode(String segment, boolean isDirectory, Layer layer) {
String name = segment + (isDirectory ? "/" : "");
for (Node child : this.children) {
if (name.equals(child.name)) {
child.layers.add(layer);
return child;
}
}
Node child = new Node(name, layer);
this.children.add(child);
Node child = this.children.computeIfAbsent(name, Node::new);
child.layers.add(layer);
return child;
}
@@ -147,7 +142,7 @@ public class LayersIndex {
index.add(this.layers.iterator().next(), name);
}
else {
for (Node child : this.children) {
for (Node child : this.children.values()) {
child.buildIndex(name, index);
}
}