Skip to content

Commit ee284f2

Browse files
committed
Add "forEachByte" variant to DataBuffer
As reported in gh-34651, `DataBuffer#getByte` can be inefficient for some implementations, as bound checks are performed for each call. This commit introduces a new `forEachByte` method that helps with traversing operations without paying the bound check cost for each byte. Closes gh-35623
1 parent 2591cab commit ee284f2

File tree

4 files changed

+87
-14
lines changed

4 files changed

+87
-14
lines changed

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,28 @@ default DataBuffer ensureCapacity(int capacity) {
186186
*/
187187
byte getByte(int index);
188188

189+
/**
190+
* Process a range of bytes from the current buffer using a
191+
* {@link ByteProcessor}.
192+
* @param index the index at which the processing will start
193+
* @param length the maximum number of bytes to be processed
194+
* @param processor the processor that consumes bytes
195+
* @return the position that was reached when processing was stopped,
196+
* or {@code -1} if the entire byte range was processed.
197+
* @throws IndexOutOfBoundsException when {@code index} is out of bounds
198+
* @since 6.2.12
199+
*/
200+
default int forEachByte(int index, int length, ByteProcessor processor) {
201+
Assert.isTrue(length >= 0, "Length must be >= 0");
202+
for (int position = index; position < index + length; position++) {
203+
byte b = getByte(position);
204+
if(!processor.process(b)) {
205+
return position;
206+
}
207+
}
208+
return -1;
209+
}
210+
189211
/**
190212
* Read a single byte from the current reading position from this data buffer.
191213
* @return the byte at this buffer's current reading position
@@ -531,4 +553,22 @@ interface ByteBufferIterator extends Iterator<ByteBuffer>, Closeable {
531553
void close();
532554
}
533555

556+
/**
557+
* Process a range of bytes one by one.
558+
* @since 6.2.12
559+
*/
560+
@FunctionalInterface
561+
interface ByteProcessor {
562+
563+
/**
564+
* Process the given {@code byte} and indicate whether processing
565+
* should continue further.
566+
* @param b a byte from the {@link DataBuffer}
567+
* @return {@code true} if processing should continue,
568+
* or {@code false} if processing should stop at this element.
569+
*/
570+
boolean process(byte b);
571+
572+
}
573+
534574
}

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

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -832,13 +832,9 @@ private static class SingleByteMatcher implements NestedMatcher {
832832

833833
@Override
834834
public int match(DataBuffer dataBuffer) {
835-
for (int pos = dataBuffer.readPosition(); pos < dataBuffer.writePosition(); pos++) {
836-
byte b = dataBuffer.getByte(pos);
837-
if (match(b)) {
838-
return pos;
839-
}
840-
}
841-
return -1;
835+
int start = dataBuffer.readPosition();
836+
int end = dataBuffer.writePosition();
837+
return dataBuffer.forEachByte(start, end - start, b -> !this.match(b));
842838
}
843839

844840
@Override
@@ -881,14 +877,13 @@ protected int getMatches() {
881877

882878
@Override
883879
public int match(DataBuffer dataBuffer) {
884-
for (int pos = dataBuffer.readPosition(); pos < dataBuffer.writePosition(); pos++) {
885-
byte b = dataBuffer.getByte(pos);
886-
if (match(b)) {
887-
reset();
888-
return pos;
889-
}
880+
int start = dataBuffer.readPosition();
881+
int end = dataBuffer.writePosition();
882+
int matchPosition = dataBuffer.forEachByte(start, end - start, b -> !this.match(b));
883+
if (matchPosition != -1) {
884+
reset();
890885
}
891-
return -1;
886+
return matchPosition;
892887
}
893888

894889
@Override

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ public byte getByte(int index) {
129129
return this.byteBuf.getByte(index);
130130
}
131131

132+
@Override
133+
public int forEachByte(int index, int length, ByteProcessor processor) {
134+
return this.byteBuf.forEachByte(index, length, processor::process);
135+
}
136+
132137
@Override
133138
public int capacity() {
134139
return this.byteBuf.capacity();

spring-core/src/test/java/org/springframework/core/io/buffer/DataBufferTests.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
import java.io.OutputStream;
2222
import java.nio.ByteBuffer;
2323
import java.nio.charset.StandardCharsets;
24+
import java.util.ArrayList;
2425
import java.util.Arrays;
26+
import java.util.List;
2527

2628
import org.springframework.core.testfixture.io.buffer.AbstractDataBufferAllocatingTests;
2729

@@ -1045,4 +1047,35 @@ void repeatedWrites(DataBufferFactory bufferFactory) {
10451047
release(buffer);
10461048
}
10471049

1050+
@ParameterizedDataBufferAllocatingTest
1051+
void forEachByteProcessAll(DataBufferFactory bufferFactory) {
1052+
super.bufferFactory = bufferFactory;
1053+
1054+
List<Byte> result = new ArrayList<>();
1055+
DataBuffer buffer = byteBuffer(new byte[]{'a', 'b', 'c', 'd'});
1056+
int index = buffer.forEachByte(0, 4, b -> {
1057+
result.add(b);
1058+
return true;
1059+
});
1060+
assertThat(index).isEqualTo(-1);
1061+
assertThat(result).containsExactly((byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd');
1062+
release(buffer);
1063+
}
1064+
1065+
1066+
@ParameterizedDataBufferAllocatingTest
1067+
void forEachByteProcessSome(DataBufferFactory bufferFactory) {
1068+
super.bufferFactory = bufferFactory;
1069+
1070+
List<Byte> result = new ArrayList<>();
1071+
DataBuffer buffer = byteBuffer(new byte[]{'a', 'b', 'c', 'd'});
1072+
int index = buffer.forEachByte(0, 4, b -> {
1073+
result.add(b);
1074+
return (b != 'c');
1075+
});
1076+
assertThat(index).isEqualTo(2);
1077+
assertThat(result).containsExactly((byte) 'a', (byte) 'b', (byte) 'c');
1078+
release(buffer);
1079+
}
1080+
10481081
}

0 commit comments

Comments
 (0)