@@ -134,6 +134,7 @@ public String readString() {
134
134
135
135
@ Override
136
136
public String readCString () {
137
+ ensureOpen ();
137
138
int size = computeCStringLength (buffer .position ());
138
139
return readString (size );
139
140
}
@@ -182,11 +183,64 @@ public void skipCString() {
182
183
buffer .position (pos + length );
183
184
}
184
185
186
+ /**
187
+ * Detects the position of the first NULL (0x00) byte in a 64-bit word using SWAR technique.
188
+ * <a href="https://en.wikipedia.org/wiki/SWAR">
189
+ */
185
190
private int computeCStringLength (final int prevPos ) {
186
- ensureOpen ();
187
- int pos = buffer .position ();
191
+ int pos = prevPos ;
188
192
int limit = buffer .limit ();
189
193
194
+ // `>>> 3` means dividing without remainder by `Long.BYTES` because `Long.BYTES` is 2^3
195
+ int chunks = (limit - pos ) >>> 3 ;
196
+ // `<< 3` means multiplying by `Long.BYTES` because `Long.BYTES` is 2^3
197
+ int toPos = pos + (chunks << 3 );
198
+ for (; pos < toPos ; pos += Long .BYTES ) {
199
+ long chunk = buffer .getLong (pos );
200
+ /*
201
+ Subtract 0x0101010101010101L to cause a borrow on 0x00 bytes.
202
+ if original byte is 00000000, then 00000000 - 00000001 = 11111111 (borrow causes the most significant bit set to 1).
203
+ */
204
+ long mask = chunk - 0x0101010101010101L ;
205
+ /*
206
+ mask will only have the most significant bit in each byte set iff it was a 0x00 byte (0x00 becomes 0xFF because of the borrow).
207
+ ~chunk will have bits that were originally 0 set to 1.
208
+ mask & ~chunk will have the most significant bit in each byte set iff original byte was 0x00.
209
+ */
210
+ mask &= ~chunk ;
211
+ /*
212
+ 0x8080808080808080:
213
+ 10000000 10000000 10000000 10000000 10000000 10000000 10000000 10000000
214
+
215
+ mask:
216
+ 00000000 00000000 11111111 00000000 00000001 00000001 00000000 00000111
217
+
218
+ ANDing mask with 0x8080808080808080 isolates the most significant bit in each byte where
219
+ the original byte was 0x00, thereby setting the most significant bit to 1 in each 0x00 original byte.
220
+
221
+ result:
222
+ 00000000 00000000 10000000 00000000 00000000 00000000 00000000 00000000
223
+ ^^^^^^^^
224
+ The most significant bit is set in each 0x00 byte, and only there.
225
+ */
226
+ mask &= 0x8080808080808080L ;
227
+ if (mask != 0 ) {
228
+ /*
229
+ The UTF-8 data is endian-independent and stored in left-to-right order in the buffer, with the first byte at the lowest index.
230
+ After calling getLong() in little-endian mode, the first UTF-8 byte ends up in the least significant byte of the long (bits 0–7),
231
+ and the last one in the most significant byte (bits 56–63).
232
+
233
+ numberOfTrailingZeros scans from the least significant bit, which aligns with the position of the first UTF-8 byte.
234
+ We then use >>> 3, which means dividing without remainder by Long.BYTES because Long.BYTES is 2^3, computing the byte offset
235
+ of the NULL terminator in the original UTF-8 data.
236
+ */
237
+ int offset = Long .numberOfTrailingZeros (mask ) >>> 3 ;
238
+ // Find the NULL terminator at pos + offset
239
+ return (pos - prevPos ) + offset + 1 ;
240
+ }
241
+ }
242
+
243
+ // Process remaining bytes one by one.
190
244
while (pos < limit ) {
191
245
if (buffer .get (pos ++) == 0 ) {
192
246
return (pos - prevPos );
0 commit comments