Avoid LinkedList performance issues through use of ArrayDeque

Closes gh-25652
This commit is contained in:
Juergen Hoeller 2020-08-27 14:14:08 +02:00
parent 60fa704f78
commit 589060d10f
3 changed files with 34 additions and 28 deletions

View File

@ -16,12 +16,12 @@
package org.springframework.beans.factory.parsing; package org.springframework.beans.factory.parsing;
import java.util.LinkedList; import java.util.ArrayDeque;
import org.springframework.lang.Nullable; 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 * a parsing process. {@link Entry entries} are added to the LinkedList at
* each point during the parse phase in a reader-specific manner. * each point during the parse phase in a reader-specific manner.
* *
@ -30,6 +30,7 @@ import org.springframework.lang.Nullable;
* error messages. * error messages.
* *
* @author Rob Harrop * @author Rob Harrop
* @author Juergen Hoeller
* @since 2.0 * @since 2.0
*/ */
public final class ParseState { public final class ParseState {
@ -40,45 +41,44 @@ public final class ParseState {
private static final char TAB = '\t'; private static final char TAB = '\t';
/** /**
* Internal {@link LinkedList} storage. * Internal {@link ArrayDeque} storage.
*/ */
private final LinkedList<Entry> state; private final ArrayDeque<Entry> state;
/** /**
* Create a new {@code ParseState} with an empty {@link LinkedList}. * Create a new {@code ParseState} with an empty {@link ArrayDeque}.
*/ */
public ParseState() { 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}. * of that of the passed in {@code ParseState}.
*/ */
@SuppressWarnings("unchecked")
private ParseState(ParseState other) { private ParseState(ParseState other) {
this.state = (LinkedList<Entry>) 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) { public void push(Entry entry) {
this.state.push(entry); this.state.push(entry);
} }
/** /**
* Remove an {@link Entry} from the {@link LinkedList}. * Remove an {@link Entry} from the {@link ArrayDeque}.
*/ */
public void pop() { public void pop() {
this.state.pop(); this.state.pop();
} }
/** /**
* Return the {@link Entry} currently at the top of the {@link LinkedList} or * Return the {@link Entry} currently at the top of the {@link ArrayDeque} or
* {@code null} if the {@link LinkedList} is empty. * {@code null} if the {@link ArrayDeque} is empty.
*/ */
@Nullable @Nullable
public Entry peek() { public Entry peek() {
@ -100,15 +100,17 @@ public final class ParseState {
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (int x = 0; x < this.state.size(); x++) { int i = 0;
if (x > 0) { for (ParseState.Entry entry : this.state) {
if (i > 0) {
sb.append('\n'); sb.append('\n');
for (int y = 0; y < x; y++) { for (int j = 0; j < i; j++) {
sb.append(TAB); sb.append(TAB);
} }
sb.append("-> "); sb.append("-> ");
} }
sb.append(this.state.get(x)); sb.append(entry);
i++;
} }
return sb.toString(); return sb.toString();
} }
@ -118,7 +120,6 @@ public final class ParseState {
* Marker interface for entries into the {@link ParseState}. * Marker interface for entries into the {@link ParseState}.
*/ */
public interface Entry { public interface Entry {
} }
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,8 +20,9 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@ -31,7 +32,7 @@ import org.springframework.lang.Nullable;
* its sibling {@link ResizableByteArrayOutputStream}. * its sibling {@link ResizableByteArrayOutputStream}.
* *
* <p>Unlike {@link java.io.ByteArrayOutputStream}, this implementation is backed * <p>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. * resizing {@code byte[]}. It does not copy buffers when it gets expanded.
* *
* <p>The initial buffer is only created when the stream is first written. * <p>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 // The buffers used to store the content bytes
private final LinkedList<byte[]> buffers = new LinkedList<>(); private final Deque<byte[]> buffers = new ArrayDeque<>();
// The size, in bytes, to use when allocating the first byte[] // The size, in bytes, to use when allocating the first byte[]
private final int initialBlockSize; private final int initialBlockSize;
@ -289,7 +290,7 @@ public class FastByteArrayOutputStream extends OutputStream {
} }
/** /**
* Create a new buffer and store it in the LinkedList * Create a new buffer and store it in the ArrayDeque.
* <p>Adds a new buffer that can store at least {@code minCapacity} bytes. * <p>Adds a new buffer that can store at least {@code minCapacity} bytes.
*/ */
private void addBuffer(int minCapacity) { private void addBuffer(int minCapacity) {

View File

@ -18,14 +18,15 @@ package org.springframework.util;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Deque;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Properties; import java.util.Properties;
@ -655,6 +656,9 @@ public abstract class StringUtils {
* inner simple dots. * inner simple dots.
* <p>The result is convenient for path comparison. For other uses, * <p>The result is convenient for path comparison. For other uses,
* notice that Windows separators ("\") are replaced by simple slashes. * notice that Windows separators ("\") are replaced by simple slashes.
* <p><strong>NOTE</strong> 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 * @param path the original path
* @return the normalized path * @return the normalized path
*/ */
@ -690,7 +694,7 @@ public abstract class StringUtils {
} }
String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR); String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR);
LinkedList<String> pathElements = new LinkedList<>(); Deque<String> pathElements = new ArrayDeque<>();
int tops = 0; int tops = 0;
for (int i = pathArray.length - 1; i >= 0; i--) { for (int i = pathArray.length - 1; i >= 0; i--) {
@ -709,7 +713,7 @@ public abstract class StringUtils {
} }
else { else {
// Normal path element found. // Normal path element found.
pathElements.add(0, element); pathElements.addFirst(element);
} }
} }
} }
@ -720,11 +724,11 @@ public abstract class StringUtils {
} }
// Remaining top paths need to be retained. // Remaining top paths need to be retained.
for (int i = 0; i < tops; i++) { 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 nothing else left, at least explicitly point to current path.
if (pathElements.size() == 1 && pathElements.getLast().isEmpty() && !prefix.endsWith(FOLDER_SEPARATOR)) { 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); return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR);