Avoid LinkedList performance issues through use of ArrayDeque
Closes gh-25652
This commit is contained in:
parent
60fa704f78
commit
589060d10f
|
@ -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 {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue