From 589060d10fa20903ec75a75bd79111f46b10a5ab Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 27 Aug 2020 14:14:08 +0200 Subject: [PATCH] Avoid LinkedList performance issues through use of ArrayDeque Closes gh-25652 --- .../beans/factory/parsing/ParseState.java | 37 ++++++++++--------- .../util/FastByteArrayOutputStream.java | 11 +++--- .../org/springframework/util/StringUtils.java | 14 ++++--- 3 files changed, 34 insertions(+), 28 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ParseState.java b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ParseState.java index 36375ef84fd6..2ff49b1b8d82 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ParseState.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/parsing/ParseState.java @@ -16,12 +16,12 @@ package org.springframework.beans.factory.parsing; -import java.util.LinkedList; +import java.util.ArrayDeque; import org.springframework.lang.Nullable; /** - * Simple {@link LinkedList}-based structure for tracking the logical position during + * Simple {@link ArrayDeque}-based structure for tracking the logical position during * a parsing process. {@link Entry entries} are added to the LinkedList at * each point during the parse phase in a reader-specific manner. * @@ -30,6 +30,7 @@ * error messages. * * @author Rob Harrop + * @author Juergen Hoeller * @since 2.0 */ public final class ParseState { @@ -40,45 +41,44 @@ public final class ParseState { private static final char TAB = '\t'; /** - * Internal {@link LinkedList} storage. + * Internal {@link ArrayDeque} storage. */ - private final LinkedList state; + private final ArrayDeque state; /** - * Create a new {@code ParseState} with an empty {@link LinkedList}. + * Create a new {@code ParseState} with an empty {@link ArrayDeque}. */ public ParseState() { - this.state = new LinkedList<>(); + this.state = new ArrayDeque<>(); } /** - * Create a new {@code ParseState} whose {@link LinkedList} is a {@link Object#clone clone} + * Create a new {@code ParseState} whose {@link ArrayDeque} is a {@link Object#clone clone} * of that of the passed in {@code ParseState}. */ - @SuppressWarnings("unchecked") private ParseState(ParseState other) { - this.state = (LinkedList) other.state.clone(); + this.state = other.state.clone(); } /** - * Add a new {@link Entry} to the {@link LinkedList}. + * Add a new {@link Entry} to the {@link ArrayDeque}. */ public void push(Entry entry) { this.state.push(entry); } /** - * Remove an {@link Entry} from the {@link LinkedList}. + * Remove an {@link Entry} from the {@link ArrayDeque}. */ public void pop() { this.state.pop(); } /** - * Return the {@link Entry} currently at the top of the {@link LinkedList} or - * {@code null} if the {@link LinkedList} is empty. + * Return the {@link Entry} currently at the top of the {@link ArrayDeque} or + * {@code null} if the {@link ArrayDeque} is empty. */ @Nullable public Entry peek() { @@ -100,15 +100,17 @@ public ParseState snapshot() { @Override public String toString() { StringBuilder sb = new StringBuilder(); - for (int x = 0; x < this.state.size(); x++) { - if (x > 0) { + int i = 0; + for (ParseState.Entry entry : this.state) { + if (i > 0) { sb.append('\n'); - for (int y = 0; y < x; y++) { + for (int j = 0; j < i; j++) { sb.append(TAB); } sb.append("-> "); } - sb.append(this.state.get(x)); + sb.append(entry); + i++; } return sb.toString(); } @@ -118,7 +120,6 @@ public String toString() { * Marker interface for entries into the {@link ParseState}. */ public interface Entry { - } } diff --git a/spring-core/src/main/java/org/springframework/util/FastByteArrayOutputStream.java b/spring-core/src/main/java/org/springframework/util/FastByteArrayOutputStream.java index f8e484fae718..d52e336e1122 100644 --- a/spring-core/src/main/java/org/springframework/util/FastByteArrayOutputStream.java +++ b/spring-core/src/main/java/org/springframework/util/FastByteArrayOutputStream.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,9 @@ import java.io.InputStream; import java.io.OutputStream; import java.security.MessageDigest; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.Iterator; -import java.util.LinkedList; import org.springframework.lang.Nullable; @@ -31,7 +32,7 @@ * its sibling {@link ResizableByteArrayOutputStream}. * *

Unlike {@link java.io.ByteArrayOutputStream}, this implementation is backed - * by a {@link java.util.LinkedList} of {@code byte[]} instead of 1 constantly + * by an {@link java.util.ArrayDeque} of {@code byte[]} instead of 1 constantly * resizing {@code byte[]}. It does not copy buffers when it gets expanded. * *

The initial buffer is only created when the stream is first written. @@ -50,7 +51,7 @@ public class FastByteArrayOutputStream extends OutputStream { // The buffers used to store the content bytes - private final LinkedList buffers = new LinkedList<>(); + private final Deque buffers = new ArrayDeque<>(); // The size, in bytes, to use when allocating the first byte[] private final int initialBlockSize; @@ -289,7 +290,7 @@ else if (size() == targetCapacity && this.buffers.getFirst().length == targetCap } /** - * Create a new buffer and store it in the LinkedList + * Create a new buffer and store it in the ArrayDeque. *

Adds a new buffer that can store at least {@code minCapacity} bytes. */ private void addBuffer(int minCapacity) { diff --git a/spring-core/src/main/java/org/springframework/util/StringUtils.java b/spring-core/src/main/java/org/springframework/util/StringUtils.java index f627c8bf8367..7867cda67134 100644 --- a/spring-core/src/main/java/org/springframework/util/StringUtils.java +++ b/spring-core/src/main/java/org/springframework/util/StringUtils.java @@ -18,14 +18,15 @@ import java.io.ByteArrayOutputStream; import java.nio.charset.Charset; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Deque; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedHashSet; -import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Properties; @@ -655,6 +656,9 @@ public static String applyRelativePath(String path, String relativePath) { * inner simple dots. *

The result is convenient for path comparison. For other uses, * notice that Windows separators ("\") are replaced by simple slashes. + *

NOTE that {@code cleanPath} should not be depended + * upon in a security context. Other mechanisms should be used to prevent + * path-traversal issues. * @param path the original path * @return the normalized path */ @@ -690,7 +694,7 @@ public static String cleanPath(String path) { } String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR); - LinkedList pathElements = new LinkedList<>(); + Deque pathElements = new ArrayDeque<>(); int tops = 0; for (int i = pathArray.length - 1; i >= 0; i--) { @@ -709,7 +713,7 @@ else if (TOP_PATH.equals(element)) { } else { // Normal path element found. - pathElements.add(0, element); + pathElements.addFirst(element); } } } @@ -720,11 +724,11 @@ else if (TOP_PATH.equals(element)) { } // Remaining top paths need to be retained. for (int i = 0; i < tops; i++) { - pathElements.add(0, TOP_PATH); + pathElements.addFirst(TOP_PATH); } // If nothing else left, at least explicitly point to current path. if (pathElements.size() == 1 && pathElements.getLast().isEmpty() && !prefix.endsWith(FOLDER_SEPARATOR)) { - pathElements.add(0, CURRENT_PATH); + pathElements.addFirst(CURRENT_PATH); } return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR);