@@ -134,6 +134,7 @@ public String readString() {
134134
135135 @ Override
136136 public String readCString () {
137+ ensureOpen ();
137138 int size = computeCStringLength (buffer .position ());
138139 return readString (size );
139140 }
@@ -182,11 +183,64 @@ public void skipCString() {
182183 buffer .position (pos + length );
183184 }
184185
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+ */
185190 private int computeCStringLength (final int prevPos ) {
186- ensureOpen ();
187- int pos = buffer .position ();
191+ int pos = prevPos ;
188192 int limit = buffer .limit ();
189193
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.
190244 while (pos < limit ) {
191245 if (buffer .get (pos ++) == 0 ) {
192246 return (pos - prevPos );
0 commit comments