Improve ConfigurationPropertyName equals/hashCode performance

Update `ConfigurationPropertyName` to improve performance of
the `equals(...)` and `hashCode()` methods by making the
following changes:

  - Move element hashCode logic to Element and cache the results
  - Exit the equals method early if hashcodes don't match
  - Exit the equals method early if toString() values match

Closes gh-44857
This commit is contained in:
Phillip Webb
2025-03-22 16:20:16 -07:00
parent 64318336c4
commit 81dee54137

View File

@@ -291,7 +291,7 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
if (getNumberOfElements() >= name.getNumberOfElements()) {
return false;
}
return elementsEqual(name);
return endsWithElementsEqualTo(name);
}
@Override
@@ -357,10 +357,20 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
&& other.elements.canShortcutWithSource(ElementType.UNIFORM)) {
return toString().equals(other.toString());
}
return elementsEqual(other);
if (hashCode() != other.hashCode()) {
return false;
}
if (toStringMatches(toString(), other.toString())) {
return true;
}
return endsWithElementsEqualTo(other);
}
private boolean elementsEqual(ConfigurationPropertyName name) {
private boolean toStringMatches(String s1, String s2) {
return s1.hashCode() == s2.hashCode() && s1.equals(s2);
}
private boolean endsWithElementsEqualTo(ConfigurationPropertyName name) {
for (int i = this.elements.getSize() - 1; i >= 0; i--) {
if (elementDiffers(this.elements, name.elements, i)) {
return false;
@@ -508,19 +518,7 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
Elements elements = this.elements;
if (hashCode == 0 && elements.getSize() != 0) {
for (int elementIndex = 0; elementIndex < elements.getSize(); elementIndex++) {
int elementHashCode = 0;
boolean indexed = elements.getType(elementIndex).isIndexed();
int length = elements.getLength(elementIndex);
for (int i = 0; i < length; i++) {
char ch = elements.charAt(elementIndex, i);
if (!indexed) {
ch = Character.toLowerCase(ch);
}
if (ElementsParser.isAlphaNumeric(ch)) {
elementHashCode = 31 * elementHashCode + ch;
}
}
hashCode = 31 * hashCode + elementHashCode;
hashCode = 31 * hashCode + elements.hashCode(elementIndex);
}
this.hashCode = hashCode;
}
@@ -737,7 +735,7 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
private static final ElementType[] NO_TYPE = {};
public static final Elements EMPTY = new Elements("", 0, NO_POSITION, NO_POSITION, NO_TYPE, null);
public static final Elements EMPTY = new Elements("", 0, NO_POSITION, NO_POSITION, NO_TYPE, null, null);
private final CharSequence source;
@@ -749,6 +747,8 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
private final ElementType[] type;
private final int[] hashCode;
/**
* Contains any resolved elements or can be {@code null} if there aren't any.
* Resolved elements allow us to modify the element values in some way (or example
@@ -759,30 +759,35 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
*/
private final CharSequence[] resolved;
Elements(CharSequence source, int size, int[] start, int[] end, ElementType[] type, CharSequence[] resolved) {
Elements(CharSequence source, int size, int[] start, int[] end, ElementType[] type, int[] hashCode,
CharSequence[] resolved) {
this.source = source;
this.size = size;
this.start = start;
this.end = end;
this.type = type;
this.hashCode = (hashCode != null) ? hashCode : new int[size];
this.resolved = resolved;
}
Elements append(Elements additional) {
int size = this.size + additional.size;
ElementType[] type = new ElementType[size];
int[] hashCode = new int[size];
System.arraycopy(this.type, 0, type, 0, this.size);
System.arraycopy(additional.type, 0, type, this.size, additional.size);
System.arraycopy(this.hashCode, 0, hashCode, 0, this.size);
System.arraycopy(additional.hashCode, 0, hashCode, this.size, additional.size);
CharSequence[] resolved = newResolved(0, size);
for (int i = 0; i < additional.size; i++) {
resolved[this.size + i] = additional.get(i);
}
return new Elements(this.source, size, this.start, this.end, type, resolved);
return new Elements(this.source, size, this.start, this.end, type, hashCode, resolved);
}
Elements chop(int size) {
CharSequence[] resolved = newResolved(0, size);
return new Elements(this.source, size, this.start, this.end, this.type, resolved);
return new Elements(this.source, size, this.start, this.end, this.type, this.hashCode, resolved);
}
Elements subElements(int offset) {
@@ -794,7 +799,9 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
System.arraycopy(this.end, offset, end, 0, size);
ElementType[] type = new ElementType[size];
System.arraycopy(this.type, offset, type, 0, size);
return new Elements(this.source, size, start, end, type, resolved);
int[] hashCode = new int[size];
System.arraycopy(this.hashCode, offset, hashCode, 0, size);
return new Elements(this.source, size, start, end, type, hashCode, resolved);
}
private CharSequence[] newResolved(int offset, int size) {
@@ -839,6 +846,25 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
return this.type[index];
}
int hashCode(int index) {
int hashCode = this.hashCode[index];
if (hashCode == 0) {
boolean indexed = getType(index).isIndexed();
int length = getLength(index);
for (int i = 0; i < length; i++) {
char ch = charAt(index, i);
if (!indexed) {
ch = Character.toLowerCase(ch);
}
if (ElementsParser.isAlphaNumeric(ch)) {
hashCode = 31 * hashCode + ch;
}
}
this.hashCode[index] = hashCode;
}
return hashCode;
}
CharSequence getSource() {
return this.source;
}
@@ -951,7 +977,7 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
type = ElementType.NON_UNIFORM;
}
add(start, length, type, valueProcessor);
return new Elements(this.source, this.size, this.start, this.end, this.type, this.resolved);
return new Elements(this.source, this.size, this.start, this.end, this.type, null, this.resolved);
}
private ElementType updateType(ElementType existingType, char ch, int index) {