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 1bf232d

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 1bf232d

File tree

1 file changed

+26
-37
lines changed

1 file changed

+26
-37
lines changed

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

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
*
6262
* @author Arjen Poutsma
6363
* @author Brian Clozel
64+
* @author Nabil Fawwaz Elqayyim
6465
* @since 5.0
6566
*/
6667
public abstract class DataBufferUtils {
@@ -859,23 +860,17 @@ public void reset() {
859860

860861

861862
/**
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>
863+
* Base {@link NestedMatcher} implementation that scans a {@link DataBuffer}
864+
* for a specific delimiter.
868865
*
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>
866+
* <p>Relies on a per-instance reusable buffer to scan data in chunks,
867+
* minimizing allocations and improving performance for large or streaming data.</p>
874868
*
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>
869+
* <p>Each matcher maintains its own state and buffer, ensuring safe use
870+
* in reactive pipelines where execution may shift across threads.</p>
871+
*
872+
* <p>Subclasses may extend this class to customize matching strategies
873+
* while reusing the built-in delimiter tracking and scanning logic.</p>
879874
*
880875
* @see NestedMatcher
881876
* @see DataBuffer
@@ -886,8 +881,7 @@ private abstract static class AbstractNestedMatcher implements NestedMatcher {
886881

887882
private int matches = 0;
888883

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

892886
protected AbstractNestedMatcher(byte[] delimiter) {
893887
this.delimiter = delimiter;
@@ -901,33 +895,29 @@ protected int getMatches() {
901895
return this.matches;
902896
}
903897

904-
protected static void releaseLocalBuffer() {
905-
LOCAL_BUFFER.remove();
906-
}
907-
908898
@Override
909899
public int match(DataBuffer dataBuffer) {
910900
final int readPos = dataBuffer.readPosition();
911901
final int writePos = dataBuffer.writePosition();
912902
final int length = writePos - readPos;
913903

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

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

911+
int matchIndex = this.matches;
912+
923913
try {
924914
for (int offset = 0; offset < length; offset += chunkSize) {
925915
int currentChunkSize = Math.min(chunkSize, length - offset);
926916

927917
dataBuffer.readPosition(readPos + offset);
928918
dataBuffer.read(chunk, 0, currentChunkSize);
929919

930-
matchIndex = processChunk(chunk, currentChunkSize, delimiter0, delimiterLen, delimiter1, matchIndex, readPos, offset);
920+
matchIndex = processChunk(chunk, currentChunkSize, delimiterBytes, delimiterLength, delimiterFirstByte, matchIndex, readPos, offset);
931921
if (matchIndex < 0) {
932922
return -(matchIndex + 1); // found, returning actual position
933923
}
@@ -938,21 +928,20 @@ public int match(DataBuffer dataBuffer) {
938928
}
939929
finally {
940930
dataBuffer.readPosition(readPos); // restore original position
941-
releaseLocalBuffer();
942931
}
943932
}
944933

945-
private int processChunk(byte[] chunk, int currentChunkSize, byte[] delimiter0, int delimiterLen, byte delimiter1, int matchIndex, int readPos, int offset) {
934+
private int processChunk(byte[] chunk, int currentChunkSize, byte[] delimiterBytes, int delimiterLen, byte delimiterFirstByte, int matchIndex, int readPos, int offset) {
946935
int i = 0;
947936
while (i < currentChunkSize) {
948937
if (matchIndex == 0) {
949-
i = findNextCandidate(chunk, i, currentChunkSize, delimiter1);
938+
i = findNextCandidate(chunk, i, currentChunkSize, delimiterFirstByte);
950939
if (i >= currentChunkSize) {
951940
return matchIndex; // no candidate in this chunk
952941
}
953942
}
954943

955-
matchIndex = updateMatchIndex(chunk[i], delimiter0, delimiterLen, delimiter1, matchIndex);
944+
matchIndex = updateMatchIndex(chunk[i], delimiterBytes, delimiterLen, delimiterFirstByte, matchIndex);
956945
if (matchIndex == -1) {
957946
return -(readPos + offset + i + 1); // return found delimiter position (encoded as negative)
958947
}
@@ -961,24 +950,24 @@ private int processChunk(byte[] chunk, int currentChunkSize, byte[] delimiter0,
961950
return matchIndex;
962951
}
963952

964-
private int findNextCandidate(byte[] chunk, int start, int limit, byte delimiter1) {
953+
private int findNextCandidate(byte[] chunk, int start, int limit, byte delimiterFirstByte) {
965954
int j = start;
966-
while (j < limit && chunk[j] != delimiter1) {
955+
while (j < limit && chunk[j] != delimiterFirstByte) {
967956
j++;
968957
}
969958
return j;
970959
}
971960

972-
private int updateMatchIndex(byte b, byte[] delimiter0, int delimiterLen, byte delimiter1, int matchIndex) {
973-
if (b == delimiter0[matchIndex]) {
961+
private int updateMatchIndex(byte b, byte[] delimiterBytes, int delimiterLen, byte delimiterFirstByte, int matchIndex) {
962+
if (b == delimiterBytes[matchIndex]) {
974963
matchIndex++;
975964
if (matchIndex == delimiterLen) {
976965
reset();
977966
return -1;
978967
}
979968
}
980969
else {
981-
matchIndex = (b == delimiter1) ? 1 : 0;
970+
matchIndex = (b == delimiterFirstByte) ? 1 : 0;
982971
}
983972
return matchIndex;
984973
}

0 commit comments

Comments
(0)

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