Optimize text replacement algorithm performance

This commit is contained in:
大阔 2025-01-03 02:23:17 +08:00
parent b838d1c52a
commit 5c8086150a

View File

@ -23,7 +23,6 @@ package me.clip.placeholderapi.replacer;
import java.util.Locale; import java.util.Locale;
import java.util.function.Function; import java.util.function.Function;
import me.clip.placeholderapi.expansion.PlaceholderExpansion; import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.ChatColor;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -33,6 +32,11 @@ public final class CharsReplacer implements Replacer {
@NotNull @NotNull
private final Closure closure; private final Closure closure;
// Cache StringBuilder to reduce object creation
private final ThreadLocal<StringBuilder> builderCache = ThreadLocal.withInitial(() -> new StringBuilder(256));
private final ThreadLocal<StringBuilder> identifierCache = ThreadLocal.withInitial(StringBuilder::new);
private final ThreadLocal<StringBuilder> parametersCache = ThreadLocal.withInitial(StringBuilder::new);
public CharsReplacer(@NotNull final Closure closure) { public CharsReplacer(@NotNull final Closure closure) {
this.closure = closure; this.closure = closure;
} }
@ -42,96 +46,109 @@ public final class CharsReplacer implements Replacer {
@Override @Override
public String apply(@NotNull final String text, @Nullable final OfflinePlayer player, public String apply(@NotNull final String text, @Nullable final OfflinePlayer player,
@NotNull final Function<String, @Nullable PlaceholderExpansion> lookup) { @NotNull final Function<String, @Nullable PlaceholderExpansion> lookup) {
if (text.indexOf(closure.head) == -1) { // Fast path - return directly if no placeholders
return text;
}
final char[] chars = text.toCharArray(); final char[] chars = text.toCharArray();
final StringBuilder builder = new StringBuilder(text.length()); final StringBuilder builder = builderCache.get();
final StringBuilder identifier = identifierCache.get();
final StringBuilder parameters = parametersCache.get();
final StringBuilder identifier = new StringBuilder(); try {
final StringBuilder parameters = new StringBuilder(); builder.setLength(0);
builder.ensureCapacity(text.length());
for (int i = 0; i < chars.length; i++) { for (int i = 0; i < chars.length; i++) {
final char l = chars[i]; final char l = chars[i];
if (l != closure.head || i + 1 >= chars.length) { if (l != closure.head || i + 1 >= chars.length) {
builder.append(l); builder.append(l);
continue;
}
boolean identified = false;
boolean invalid = true;
boolean hadSpace = false;
while (++i < chars.length) {
final char p = chars[i];
if (p == ' ' && !identified) {
hadSpace = true;
break;
}
if (p == closure.tail) {
invalid = false;
break;
}
if (p == '_' && !identified) {
identified = true;
continue; continue;
} }
if (identified) { boolean identified = false;
parameters.append(p); boolean invalid = true;
} else { boolean hadSpace = false;
identifier.append(p);
identifier.setLength(0);
parameters.setLength(0);
while (++i < chars.length) {
final char p = chars[i];
if (p == ' ' && !identified) {
hadSpace = true;
break;
}
if (p == closure.tail) {
invalid = false;
break;
}
if (p == '_' && !identified) {
identified = true;
continue;
}
if (identified) {
parameters.append(p);
} else {
identifier.append(p);
}
} }
final String identifierString = identifier.toString();
final String lowercaseIdentifierString = identifierString.toLowerCase(Locale.ROOT);
final String parametersString = parameters.toString();
if (invalid) {
builder.append(closure.head).append(identifierString);
if (identified) {
builder.append('_').append(parametersString);
}
if (hadSpace) {
builder.append(' ');
}
continue;
}
final PlaceholderExpansion placeholder = lookup.apply(lowercaseIdentifierString);
if (placeholder == null) {
builder.append(closure.head).append(identifierString);
if (identified) {
builder.append('_');
}
builder.append(parametersString).append(closure.tail);
continue;
}
final String replacement = placeholder.onRequest(player, parametersString);
if (replacement == null) {
builder.append(closure.head).append(identifierString);
if (identified) {
builder.append('_');
}
builder.append(parametersString).append(closure.tail);
continue;
}
builder.append(replacement);
} }
final String identifierString = identifier.toString(); return builder.toString();
final String lowercaseIdentifierString = identifierString.toLowerCase(Locale.ROOT); } finally {
final String parametersString = parameters.toString(); // Reset cached StringBuilder
builder.setLength(0);
identifier.setLength(0); identifier.setLength(0);
parameters.setLength(0); parameters.setLength(0);
if (invalid) {
builder.append(closure.head).append(identifierString);
if (identified) {
builder.append('_').append(parametersString);
}
if (hadSpace) {
builder.append(' ');
}
continue;
}
final PlaceholderExpansion placeholder = lookup.apply(lowercaseIdentifierString);
if (placeholder == null) {
builder.append(closure.head).append(identifierString);
if (identified) {
builder.append('_');
}
builder.append(parametersString).append(closure.tail);
continue;
}
final String replacement = placeholder.onRequest(player, parametersString);
if (replacement == null) {
builder.append(closure.head).append(identifierString);
if (identified) {
builder.append('_');
}
builder.append(parametersString).append(closure.tail);
continue;
}
builder.append(replacement);
} }
return builder.toString();
} }
} }