Skip to content

Commit 816ddcd

Browse files
committed
JAVA-5789 Implements batch ByteBuf::indexOf
1 parent 1c9942c commit 816ddcd

File tree

7 files changed

+161
-7
lines changed

7 files changed

+161
-7
lines changed

Diff for: bson/src/main/org/bson/ByteBuf.java

+17
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,23 @@ public interface ByteBuf {
168168
*/
169169
ByteBuf clear();
170170

171+
172+
/**
173+
* Returns the index within this buffer of the first occurrence of the given byte
174+
*
175+
* @param b The byte to search for
176+
* @return The index within this buffer of the first occurrence of the given byte, starting the search at the specified index, or {@code
177+
* -1} if the byte does not occur before the limit
178+
*/
179+
default int indexOf(byte b) {
180+
for (int i = position(); i < limit(); i++) {
181+
if (get(i) == b) {
182+
return i;
183+
}
184+
}
185+
return -1;
186+
}
187+
171188
/**
172189
* Modifies this buffer's byte order.
173190
*

Diff for: bson/src/main/org/bson/ByteBufNIO.java

+25
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,31 @@ public void release() {
6464
}
6565
}
6666

67+
@Override
68+
public int indexOf(final byte b) {
69+
ByteBuffer buf = this.buf;
70+
// readonly buffers won't go into the fast-path
71+
if (buf.hasArray()) {
72+
byte[] array = buf.array();
73+
int offset = buf.arrayOffset() + buf.position();
74+
int length = buf.remaining();
75+
for (int i = 0; i < length; i++) {
76+
if (array[offset + i] == b) {
77+
return i;
78+
}
79+
}
80+
return -1;
81+
}
82+
int position = buf.position();
83+
int limit = buf.limit();
84+
for (int i = position; i < limit; i++) {
85+
if (buf.get(i) == b) {
86+
return i - position;
87+
}
88+
}
89+
return -1;
90+
}
91+
6792
@Override
6893
public int capacity() {
6994
return buf.capacity();

Diff for: bson/src/main/org/bson/io/ByteBufferBsonInput.java

+5-6
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,12 @@ private String readString(final int size) {
159159
@Override
160160
public void skipCString() {
161161
ensureOpen();
162-
boolean checkNext = true;
163-
while (checkNext) {
164-
if (!buffer.hasRemaining()) {
165-
throw new BsonSerializationException("Found a BSON string that is not null-terminated");
166-
}
167-
checkNext = buffer.get() != 0;
162+
int indexOfZero = buffer.indexOf((byte) 0);
163+
if (indexOfZero == -1) {
164+
buffer.position(buffer.limit());
165+
throw new BsonSerializationException("Found a BSON string that is not null-terminated");
168166
}
167+
buffer.position(indexOfZero + 1);
169168
}
170169

171170
@Override

Diff for: driver-core/src/main/com/mongodb/internal/connection/CompositeByteBuf.java

+23-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ class CompositeByteBuf implements ByteBuf {
4242

4343
int offset = 0;
4444
for (ByteBuf cur : buffers) {
45-
Component component = new Component(cur.asReadOnly().order(ByteOrder.LITTLE_ENDIAN), offset);
45+
// since we don't expose any method to modify the buffer nor expose its array we can avoid using read-only ones
46+
// to speed up ByteBufNIO::indexOf, is it's the concrete type used
47+
Component component = new Component(cur.duplicate().order(ByteOrder.LITTLE_ENDIAN), offset);
4648
components.add(component);
4749
offset = component.endOffset;
4850
}
@@ -63,6 +65,18 @@ public ByteBuf order(final ByteOrder byteOrder) {
6365
return this;
6466
}
6567

68+
@Override
69+
public int indexOf(final byte b) {
70+
// use this pattern to save creating a new iterator
71+
for (int i = 0, size = components.size(); i < size; i++) {
72+
int index = components.get(i).indexOf(b);
73+
if (index != -1) {
74+
return index;
75+
}
76+
}
77+
return -1;
78+
}
79+
6680
@Override
6781
public int capacity() {
6882
return components.get(components.size() - 1).endOffset;
@@ -340,5 +354,13 @@ private static final class Component {
340354
this.offset = offset;
341355
this.endOffset = offset + length;
342356
}
357+
358+
public int indexOf(final byte b) {
359+
int i = buffer.indexOf(b);
360+
if (i != -1) {
361+
return i + offset;
362+
}
363+
return -1;
364+
}
343365
}
344366
}

Diff for: driver-core/src/main/com/mongodb/internal/connection/netty/NettyByteBuf.java

+10
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,16 @@ public ByteBuf put(final byte b) {
8989
return this;
9090
}
9191

92+
@Override
93+
public int indexOf(final byte b) {
94+
int position = isWriting ? proxied.writerIndex() : proxied.readerIndex();
95+
int limit = position + (isWriting ? proxied.writableBytes() : proxied.readableBytes());
96+
if (position == limit) {
97+
return -1;
98+
}
99+
return proxied.indexOf(position, limit, b);
100+
}
101+
92102
@Override
93103
public ByteBuf flip() {
94104
isWriting = !isWriting;

Diff for: driver-core/src/test/unit/com/mongodb/internal/connection/ByteBufSpecification.groovy

+50
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,56 @@ import org.bson.ByteBuf
2424
import spock.lang.Specification
2525

2626
class ByteBufSpecification extends Specification {
27+
28+
29+
def "should find the first occurrence of a byte"() {
30+
given:
31+
def buffer = provider.getBuffer(1024)
32+
33+
when:
34+
buffer.with {
35+
put((byte) 1)
36+
put((byte) 2)
37+
put((byte) 3)
38+
put((byte) 4)
39+
put((byte) 5)
40+
flip()
41+
}
42+
43+
then:
44+
buffer.indexOf((byte) 3) == 2
45+
46+
cleanup:
47+
buffer.release()
48+
49+
where:
50+
provider << [new NettyBufferProvider(), new SimpleBufferProvider()]
51+
}
52+
53+
def "should not find any occurrence of a byte"() {
54+
given:
55+
def buffer = provider.getBuffer(1024)
56+
57+
when:
58+
buffer.with {
59+
put((byte) 1)
60+
put((byte) 2)
61+
put((byte) 3)
62+
put((byte) 4)
63+
put((byte) 5)
64+
flip()
65+
}
66+
67+
then:
68+
buffer.indexOf((byte) 6) == -1
69+
70+
cleanup:
71+
buffer.release()
72+
73+
where:
74+
provider << [new NettyBufferProvider(), new SimpleBufferProvider()]
75+
}
76+
2777
def 'should put a byte'() {
2878
given:
2979
def buffer = provider.getBuffer(1024)

Diff for: driver-core/src/test/unit/com/mongodb/internal/connection/CompositeByteBufSpecification.groovy

+31
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,37 @@ class CompositeByteBufSpecification extends Specification {
290290
!buf.hasRemaining()
291291
}
292292

293+
def "should find the first occurrence of a byte"() {
294+
given:
295+
def buf = new CompositeByteBuf([
296+
new ByteBufNIO(ByteBuffer.wrap([5, 5, 5, 5] as byte[])),
297+
new ByteBufNIO(ByteBuffer.wrap([1, 2, 3, 4] as byte[]))])
298+
when:
299+
byte b = 3;
300+
301+
then:
302+
buf.indexOf(b) == 6
303+
304+
cleanup:
305+
buf.release()
306+
}
307+
308+
def "should not find any occurrence of a byte"() {
309+
given:
310+
def buf = new CompositeByteBuf([
311+
new ByteBufNIO(ByteBuffer.wrap([5, 5, 5, 5] as byte[])),
312+
new ByteBufNIO(ByteBuffer.wrap([1, 2, 3, 4] as byte[]))])
313+
314+
when:
315+
byte b = 6;
316+
317+
then:
318+
buf.indexOf(b) == -1
319+
320+
cleanup:
321+
buf.release()
322+
}
323+
293324
def 'absolute getInt should read little endian integer and preserve position'() {
294325
given:
295326
def byteBuffer = new ByteBufNIO(ByteBuffer.wrap([1, 2, 3, 4] as byte[]))

0 commit comments

Comments
 (0)