Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 73a2f0a

Browse files
Refactor AbstractNestedMatcher to use per-instance buffer
- Replace ThreadLocal buffer with a per-instance reusable buffer - Improves memory locality and reduces ThreadLocal overhead - Update Javadoc for clarity, performance notes, and subclassing guidance Closes gh-34651 Signed-off-by: Nabil Fawwaz Elqayyim <master@nabilfawwaz.com>
1 parent bcfae82 commit 73a2f0a

File tree

1 file changed

+25
-37
lines changed

1 file changed

+25
-37
lines changed

‎spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java‎

Lines changed: 25 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -859,23 +859,17 @@ public void reset() {
859859

860860

861861
/**
862-
* An abstract base implementation of {@link NestedMatcher} that looks for
863-
* a specific delimiter in a {@link DataBuffer}.
864-
* <p>
865-
* Uses a thread-local buffer to scan data in chunks, reducing memory
866-
* allocations and improving performance when processing large buffers.
867-
* </p>
862+
* Base {@link NestedMatcher} implementation that scans a {@link DataBuffer}
863+
* for a specific delimiter.
868864
*
869-
* <p>
870-
* Each matcher keeps its own match state, so it is intended for
871-
* single-threaded use. The thread-local buffer ensures that multiple
872-
* threads can run their own matchers independently without interfering.
873-
* </p>
865+
* <p>Relies on a per-instance reusable buffer to scan data in chunks,
866+
* minimizing allocations and improving performance for large or streaming data.</p>
874867
*
875-
* <p>
876-
* Subclasses can extend this class to add custom matching behavior while
877-
* reusing the built-in delimiter tracking and scanning logic.
878-
* </p>
868+
* <p>Each matcher maintains its own state and buffer, ensuring safe use
869+
* in reactive pipelines where execution may shift across threads.</p>
870+
*
871+
* <p>Subclasses may extend this class to customize matching strategies
872+
* while reusing the built-in delimiter tracking and scanning logic.</p>
879873
*
880874
* @see NestedMatcher
881875
* @see DataBuffer
@@ -886,8 +880,7 @@ private abstract static class AbstractNestedMatcher implements NestedMatcher {
886880

887881
private int matches = 0;
888882

889-
// Thread-local chunk buffer to avoid per-call allocations
890-
private static final ThreadLocal<byte[]> LOCAL_BUFFER = ThreadLocal.withInitial(() -> new byte[8 * 1024]);
883+
private final byte[] localBuffer = new byte[8 * 1024]; // Reusable buffer per matcher instance
891884

892885
protected AbstractNestedMatcher(byte[] delimiter) {
893886
this.delimiter = delimiter;
@@ -901,33 +894,29 @@ protected int getMatches() {
901894
return this.matches;
902895
}
903896

904-
protected static void releaseLocalBuffer() {
905-
LOCAL_BUFFER.remove();
906-
}
907-
908897
@Override
909898
public int match(DataBuffer dataBuffer) {
910899
final int readPos = dataBuffer.readPosition();
911900
final int writePos = dataBuffer.writePosition();
912901
final int length = writePos - readPos;
913902

914-
final byte[] delimiter0 = this.delimiter;
915-
final int delimiterLen = delimiter0.length;
916-
final byte delimiter1 = delimiter0[0];
903+
final byte[] delimiterBytes = this.delimiter;
904+
final int delimiterLength = delimiterBytes.length;
905+
final byte delimiterFirstByte = delimiterBytes[0];
917906

918-
int matchIndex = this.matches;
919-
920-
final byte[] chunk = LOCAL_BUFFER.get();
907+
final byte[] chunk = localBuffer;
921908
final int chunkSize = Math.min(chunk.length, length);
922909

910+
int matchIndex = this.matches;
911+
923912
try {
924913
for (int offset = 0; offset < length; offset += chunkSize) {
925914
int currentChunkSize = Math.min(chunkSize, length - offset);
926915

927916
dataBuffer.readPosition(readPos + offset);
928917
dataBuffer.read(chunk, 0, currentChunkSize);
929918

930-
matchIndex = processChunk(chunk, currentChunkSize, delimiter0, delimiterLen, delimiter1, matchIndex, readPos, offset);
919+
matchIndex = processChunk(chunk, currentChunkSize, delimiterBytes, delimiterLength, delimiterFirstByte, matchIndex, readPos, offset);
931920
if (matchIndex < 0) {
932921
return -(matchIndex + 1); // found, returning actual position
933922
}
@@ -938,21 +927,20 @@ public int match(DataBuffer dataBuffer) {
938927
}
939928
finally {
940929
dataBuffer.readPosition(readPos); // restore original position
941-
releaseLocalBuffer();
942930
}
943931
}
944932

945-
private int processChunk(byte[] chunk, int currentChunkSize, byte[] delimiter0, int delimiterLen, byte delimiter1, int matchIndex, int readPos, int offset) {
933+
private int processChunk(byte[] chunk, int currentChunkSize, byte[] delimiterBytes, int delimiterLen, byte delimiterFirstByte, int matchIndex, int readPos, int offset) {
946934
int i = 0;
947935
while (i < currentChunkSize) {
948936
if (matchIndex == 0) {
949-
i = findNextCandidate(chunk, i, currentChunkSize, delimiter1);
937+
i = findNextCandidate(chunk, i, currentChunkSize, delimiterFirstByte);
950938
if (i >= currentChunkSize) {
951939
return matchIndex; // no candidate in this chunk
952940
}
953941
}
954942

955-
matchIndex = updateMatchIndex(chunk[i], delimiter0, delimiterLen, delimiter1, matchIndex);
943+
matchIndex = updateMatchIndex(chunk[i], delimiterBytes, delimiterLen, delimiterFirstByte, matchIndex);
956944
if (matchIndex == -1) {
957945
return -(readPos + offset + i + 1); // return found delimiter position (encoded as negative)
958946
}
@@ -961,24 +949,24 @@ private int processChunk(byte[] chunk, int currentChunkSize, byte[] delimiter0,
961949
return matchIndex;
962950
}
963951

964-
private int findNextCandidate(byte[] chunk, int start, int limit, byte delimiter1) {
952+
private int findNextCandidate(byte[] chunk, int start, int limit, byte delimiterFirstByte) {
965953
int j = start;
966-
while (j < limit && chunk[j] != delimiter1) {
954+
while (j < limit && chunk[j] != delimiterFirstByte) {
967955
j++;
968956
}
969957
return j;
970958
}
971959

972-
private int updateMatchIndex(byte b, byte[] delimiter0, int delimiterLen, byte delimiter1, int matchIndex) {
973-
if (b == delimiter0[matchIndex]) {
960+
private int updateMatchIndex(byte b, byte[] delimiterBytes, int delimiterLen, byte delimiterFirstByte, int matchIndex) {
961+
if (b == delimiterBytes[matchIndex]) {
974962
matchIndex++;
975963
if (matchIndex == delimiterLen) {
976964
reset();
977965
return -1;
978966
}
979967
}
980968
else {
981-
matchIndex = (b == delimiter1) ? 1 : 0;
969+
matchIndex = (b == delimiterFirstByte) ? 1 : 0;
982970
}
983971
return matchIndex;
984972
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /