Turn ByteIterator into ByteCursor

Motivation:
Cursors are better than iterators in that they only need to check boundary conditions once per iteration, when processed in a loop.
This should make them easier for the compiler to optimise.

Modification:
Change the ByteIterator to a ByteCursor. The API is almost the same, but with a few subtle differences in semantics.
The primary difference is that the cursor movement and boundary condition checking and position movement happen at the same time, and do not need to occur when the values are fetched out of the cursor.
An iterator, on the other hand, needs to throw an exception if "next" is called too many times.

Result:
Simpler code, and hopefully faster code as well.
This commit is contained in:
Chris Vest 2020-12-02 14:29:40 +01:00
parent 3aeebdd058
commit 6cc49c1c62
6 changed files with 386 additions and 366 deletions

View File

@ -302,9 +302,9 @@ public interface Buf extends Rc<Buf>, BufAccessors {
* {@linkplain #readerOffset() reader offset} and {@linkplain #writerOffset() writer offset} are not modified while * {@linkplain #readerOffset() reader offset} and {@linkplain #writerOffset() writer offset} are not modified while
* the iteration takes place. Otherwise unpredictable behaviour might result. * the iteration takes place. Otherwise unpredictable behaviour might result.
* *
* @return A {@link ByteIterator} for the readable bytes of this buffer. * @return A {@link ByteCursor} for the readable bytes of this buffer.
*/ */
default ByteIterator iterate() { default ByteCursor iterate() {
return iterate(readerOffset(), readableBytes()); return iterate(readerOffset(), readableBytes());
} }
@ -320,11 +320,11 @@ public interface Buf extends Rc<Buf>, BufAccessors {
* @param fromOffset The offset into the buffer where iteration should start. * @param fromOffset The offset into the buffer where iteration should start.
* The first byte read from the iterator will be the byte at this offset. * The first byte read from the iterator will be the byte at this offset.
* @param length The number of bytes to iterate. * @param length The number of bytes to iterate.
* @return A {@link ByteIterator} for the given stretch of bytes of this buffer. * @return A {@link ByteCursor} for the given stretch of bytes of this buffer.
* @throws IllegalArgumentException if the length is negative, or if the region given by the {@code fromOffset} and * @throws IllegalArgumentException if the length is negative, or if the region given by the {@code fromOffset} and
* the {@code length} reaches outside of the bounds of this buffer. * the {@code length} reaches outside of the bounds of this buffer.
*/ */
ByteIterator iterate(int fromOffset, int length); ByteCursor iterate(int fromOffset, int length);
/** /**
* Iterate the readable bytes of this buffer, in reverse. The {@linkplain #readerOffset() reader offset} and * Iterate the readable bytes of this buffer, in reverse. The {@linkplain #readerOffset() reader offset} and
@ -334,9 +334,9 @@ public interface Buf extends Rc<Buf>, BufAccessors {
* {@linkplain #readerOffset() reader offset} and {@linkplain #writerOffset() writer offset} are not modified while * {@linkplain #readerOffset() reader offset} and {@linkplain #writerOffset() writer offset} are not modified while
* the iteration takes place. Otherwise unpredictable behaviour might result. * the iteration takes place. Otherwise unpredictable behaviour might result.
* *
* @return A {@link ByteIterator} for the readable bytes of this buffer. * @return A {@link ByteCursor} for the readable bytes of this buffer.
*/ */
default ByteIterator iterateReverse() { default ByteCursor iterateReverse() {
int woff = writerOffset(); int woff = writerOffset();
return iterateReverse(woff == 0? 0 : woff - 1, readableBytes()); return iterateReverse(woff == 0? 0 : woff - 1, readableBytes());
} }
@ -353,11 +353,11 @@ public interface Buf extends Rc<Buf>, BufAccessors {
* @param fromOffset The offset into the buffer where iteration should start. * @param fromOffset The offset into the buffer where iteration should start.
* The first byte read from the iterator will be the byte at this offset. * The first byte read from the iterator will be the byte at this offset.
* @param length The number of bytes to iterate. * @param length The number of bytes to iterate.
* @return A {@link ByteIterator} for the given stretch of bytes of this buffer. * @return A {@link ByteCursor} for the given stretch of bytes of this buffer.
* @throws IllegalArgumentException if the length is negative, or if the region given by the {@code fromOffset} and * @throws IllegalArgumentException if the length is negative, or if the region given by the {@code fromOffset} and
* the {@code length} reaches outside of the bounds of this buffer. * the {@code length} reaches outside of the bounds of this buffer.
*/ */
ByteIterator iterateReverse(int fromOffset, int length); ByteCursor iterateReverse(int fromOffset, int length);
/** /**
* Ensure that this buffer has {@linkplain #writableBytes() available space for writing} the given number of * Ensure that this buffer has {@linkplain #writableBytes() available space for writing} the given number of

View File

@ -17,48 +17,55 @@ package io.netty.buffer.api;
import io.netty.util.ByteProcessor; import io.netty.util.ByteProcessor;
/** /**
* The ByteIterator scans through a sequence of bytes. * The ByteCursor scans through a sequence of bytes.
* This is similar to {@link ByteProcessor}, but for external iteration rather than internal iteration. * This is similar to {@link ByteProcessor}, but for external iteration rather than internal iteration.
* The external iteration allows the callers to control the pace of the iteration. * The external iteration allows the callers to control the pace of the iteration.
* The API includes methods for reading {@code long}s as a batch of 8 bytes. * The API includes methods for reading {@code long}s as a batch of 8 bytes.
* The long values are always in big-endian format, so that the highest-order byte in the long value, contain the byte * The long values are always in big-endian format, so that the highest-order byte in the long value, contain the byte
* that would otherwise have been returned by the next call to {@link #nextByte()}. * that would otherwise have been returned by the next call to {@link #getByte()}.
*/ */
public interface ByteIterator { public interface ByteCursor {
/** /**
* Check if the iterator has at least 8 bytes left. * Check if the iterator has at least 8 bytes left, and if so, read those 8 bytes and move the cursor forward.
* Note that when this method returns {@code false}, the {@link #hasNextByte()} can still return {@code true}. * The bytes are packed as a {@code long} value in big-endian format, such that the highest-order byte
* in the long, is the byte that would otherwise have been returned by the next call to {@link #getByte()},
* after a call to {@link #nextByte()}.
* The bytes (as a {@code long}) will then be available through the {@link #getLong()} method.
* <p>
* Note that when this method returns {@code false}, the {@link #nextByte()} can still return {@code true}.
* It is recommended to have any long-processing loop be followed by a byte-processing loop for the 7 or fewer * It is recommended to have any long-processing loop be followed by a byte-processing loop for the 7 or fewer
* bytes that might form a tail in the iterator. * bytes that might form a tail in the cursor.
* <p>
* Also note that this method will not influence what is returned the {@link #getByte()} method.
* *
* @return {@code true} if a call to {@link #nextLong()} would succeed, otherwise {@code false}. * @return {@code true} if the cursor read 8 bytes and moved forward, otherwise {@code false}.
*/ */
boolean hasNextLong(); boolean nextLong();
/** /**
* Read and return the next 8 bytes, and move the iterator position forward by 8 bytes. * Return the last 8 bytes read by {@link #nextLong()}.
* The bytes are packed and return as a {@code long} value in big-endian format, such that the highest-order byte
* in the long, is the byte that would otherwise have been returned by the next call to {@link #nextByte()}.
* *
* @return The next 8 bytes in big-endian format. * @return The 8 bytes, in big-endian format, that was read by the most recent successful call to
* @throws java.util.NoSuchElementException If the iterator has fewer than 8 bytes left. * {@link #nextLong()}.
*/ */
long nextLong(); long getLong();
/** /**
* Check if the iterator has at least one byte left. * Check if the iterator has at least one byte left, and if so, read that byte and move the cursor forward.
* The byte will then be available through the {@link #getByte()}.
* <p>
* Note that this method will not influence what is returned from the {@link #getLong()} method.
* *
* @return {@code true} if the next call to {@link #nextByte()} would succeed, otherwise {@code false}. * @return {@code true} if the cursor read a byte and moved forward, otherwise {@code false}.
*/ */
boolean hasNextByte(); boolean nextByte();
/** /**
* Read and return the next byte, and move the iterator position] forward by one byte. * Return the last byte that was read by {@link #nextByte()}.
* *
* @return The next byte. * @return The next byte that was read by the most recent successful call to {@link #nextByte()}.
* @throws java.util.NoSuchElementException If the iterator has no more bytes left.
*/ */
byte nextByte(); byte getByte();
/** /**
* The current position of this iterator into the underlying sequence of bytes. * The current position of this iterator into the underlying sequence of bytes.
@ -85,14 +92,10 @@ public interface ByteIterator {
*/ */
default int process(ByteProcessor processor) { default int process(ByteProcessor processor) {
boolean requestMore = true; boolean requestMore = true;
int index = currentOffset(); int count = 0;
if (hasNextByte()) { while (nextByte() && (requestMore = processor.process(getByte()))) {
byte val = nextByte(); count++;
while ((requestMore = processor.process(val)) && hasNextByte()) {
val = nextByte();
index++;
}
} }
return requestMore? -1 : index; return requestMore? -1 : count;
} }
} }

View File

@ -293,18 +293,17 @@ final class CompositeBuf extends RcSupport<Buf, CompositeBuf> implements Buf {
// Iterate in reverse to account for src and dest buffer overlap. // Iterate in reverse to account for src and dest buffer overlap.
// todo optimise by delegating to constituent buffers. // todo optimise by delegating to constituent buffers.
var itr = iterateReverse(srcPos + length - 1, length); var cursor = iterateReverse(srcPos + length - 1, length);
ByteOrder prevOrder = dest.order(); ByteOrder prevOrder = dest.order();
// We read longs in BE, in reverse, so they need to be flipped for writing. // We read longs in BE, in reverse, so they need to be flipped for writing.
dest.order(ByteOrder.LITTLE_ENDIAN); dest.order(ByteOrder.LITTLE_ENDIAN);
try { try {
while (itr.hasNextLong()) { while (cursor.nextLong()) {
long val = itr.nextLong();
length -= Long.BYTES; length -= Long.BYTES;
dest.setLong(destPos + length, val); dest.setLong(destPos + length, cursor.getLong());
} }
while (itr.hasNextByte()) { while (cursor.nextByte()) {
dest.setByte(destPos + --length, itr.nextByte()); dest.setByte(destPos + --length, cursor.getByte());
} }
} finally { } finally {
dest.order(prevOrder); dest.order(prevOrder);
@ -312,7 +311,7 @@ final class CompositeBuf extends RcSupport<Buf, CompositeBuf> implements Buf {
} }
@Override @Override
public ByteIterator iterate(int fromOffset, int length) { public ByteCursor iterate(int fromOffset, int length) {
if (fromOffset < 0) { if (fromOffset < 0) {
throw new IllegalArgumentException("The fromOffset cannot be negative: " + fromOffset + '.'); throw new IllegalArgumentException("The fromOffset cannot be negative: " + fromOffset + '.');
} }
@ -326,61 +325,63 @@ final class CompositeBuf extends RcSupport<Buf, CompositeBuf> implements Buf {
int startBufferIndex = searchOffsets(fromOffset); int startBufferIndex = searchOffsets(fromOffset);
int off = fromOffset - offsets[startBufferIndex]; int off = fromOffset - offsets[startBufferIndex];
Buf startBuf = bufs[startBufferIndex]; Buf startBuf = bufs[startBufferIndex];
ByteIterator startIterator = startBuf.iterate(off, Math.min(startBuf.capacity() - off, length)); ByteCursor startCursor = startBuf.iterate(off, Math.min(startBuf.capacity() - off, length));
return new ByteIterator() { return new ByteCursor() {
int index = fromOffset; int index = fromOffset;
final int end = fromOffset + length; final int end = fromOffset + length;
int bufferIndex = startBufferIndex; int bufferIndex = startBufferIndex;
ByteIterator itr = startIterator; ByteCursor cursor = startCursor;
long longValue = -1;
byte byteValue = -1;
@Override @Override
public boolean hasNextLong() { public boolean nextLong() {
return bytesLeft() >= Long.BYTES; if (bytesLeft() >= Long.BYTES) {
} if (cursor.nextLong()) {
longValue = cursor.getLong();
@Override index += Long.BYTES;
public long nextLong() { } else {
if (itr.hasNextLong()) { longValue = nextLongFromBytes(); // Leave index increments to 'nextByte'
long val = itr.nextLong(); }
index += Long.BYTES; return true;
return val;
} }
if (!hasNextLong()) { return false;
throw new NoSuchElementException();
}
return nextLongFromBytes(); // Leave index increments to 'nextByte'
} }
private long nextLongFromBytes() { private long nextLongFromBytes() {
long val = 0; long val = 0;
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
nextByte();
val <<= 8; val <<= 8;
val |= nextByte(); val |= getByte();
} }
return val; return val;
} }
@Override @Override
public boolean hasNextByte() { public long getLong() {
return index < end; return longValue;
} }
@Override @Override
public byte nextByte() { public boolean nextByte() {
if (itr.hasNextByte()) { if (index < end) {
byte val = itr.nextByte(); if (!cursor.nextByte()) {
bufferIndex++;
Buf nextBuf = bufs[bufferIndex];
cursor = nextBuf.iterate(0, Math.min(nextBuf.capacity(), bytesLeft()));
cursor.nextByte();
}
byteValue = cursor.getByte();
index++; index++;
return val; return true;
} }
if (!hasNextByte()) { return false;
throw new NoSuchElementException(); }
}
bufferIndex++; @Override
Buf nextBuf = bufs[bufferIndex]; public byte getByte() {
itr = nextBuf.iterate(0, Math.min(nextBuf.capacity(), bytesLeft())); return byteValue;
byte val = itr.nextByte();
index++;
return val;
} }
@Override @Override
@ -396,7 +397,7 @@ final class CompositeBuf extends RcSupport<Buf, CompositeBuf> implements Buf {
} }
@Override @Override
public ByteIterator iterateReverse(int fromOffset, int length) { public ByteCursor iterateReverse(int fromOffset, int length) {
if (fromOffset < 0) { if (fromOffset < 0) {
throw new IllegalArgumentException("The fromOffset cannot be negative: " + fromOffset + '.'); throw new IllegalArgumentException("The fromOffset cannot be negative: " + fromOffset + '.');
} }
@ -410,60 +411,64 @@ final class CompositeBuf extends RcSupport<Buf, CompositeBuf> implements Buf {
int startBufferIndex = searchOffsets(fromOffset); int startBufferIndex = searchOffsets(fromOffset);
int off = fromOffset - offsets[startBufferIndex]; int off = fromOffset - offsets[startBufferIndex];
Buf startBuf = bufs[startBufferIndex]; Buf startBuf = bufs[startBufferIndex];
ByteIterator startIterator = startBuf.iterateReverse(off, Math.min(off + 1, length)); ByteCursor startCursor = startBuf.iterateReverse(off, Math.min(off + 1, length));
return new ByteIterator() { return new ByteCursor() {
int index = fromOffset; int index = fromOffset;
final int end = fromOffset - length; final int end = fromOffset - length;
int bufferIndex = startBufferIndex; int bufferIndex = startBufferIndex;
ByteIterator itr = startIterator; ByteCursor cursor = startCursor;
long longValue = -1;
byte byteValue = -1;
@Override @Override
public boolean hasNextLong() { public boolean nextLong() {
return bytesLeft() >= Long.BYTES; if (bytesLeft() >= Long.BYTES) {
} if (cursor.nextLong()) {
index -= Long.BYTES;
@Override longValue = cursor.getLong();
public long nextLong() { } else {
if (itr.hasNextLong()) { longValue = nextLongFromBytes(); // Leave index increments to 'nextByte'
index -= Long.BYTES; }
return itr.nextLong(); return true;
} }
if (!hasNextLong()) { return false;
throw new NoSuchElementException();
}
return nextLongFromBytes(); // Leave index increments to 'nextByte'
} }
private long nextLongFromBytes() { private long nextLongFromBytes() {
long val = 0; long val = 0;
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
nextByte();
val <<= 8; val <<= 8;
val |= nextByte(); val |= getByte();
} }
return val; return val;
} }
@Override @Override
public boolean hasNextByte() { public long getLong() {
return index > end; return longValue;
} }
@Override @Override
public byte nextByte() { public boolean nextByte() {
if (itr.hasNextByte()) { if (index > end) {
byte val = itr.nextByte(); if (!cursor.nextByte()) {
bufferIndex--;
Buf nextBuf = bufs[bufferIndex];
int length = Math.min(nextBuf.capacity(), bytesLeft());
cursor = nextBuf.iterateReverse(nextBuf.capacity() - 1, length);
cursor.nextByte();
}
byteValue = cursor.getByte();
index--; index--;
return val; return true;
} }
if (!hasNextByte()) { return false;
throw new NoSuchElementException(); }
}
bufferIndex--; @Override
Buf nextBuf = bufs[bufferIndex]; public byte getByte() {
itr = nextBuf.iterateReverse(nextBuf.capacity() - 1, Math.min(nextBuf.capacity(), bytesLeft())); return byteValue;
byte val = itr.nextByte();
index--;
return val;
} }
@Override @Override

View File

@ -18,7 +18,7 @@ package io.netty.buffer.api.memseg;
import io.netty.buffer.api.Allocator; import io.netty.buffer.api.Allocator;
import io.netty.buffer.api.AllocatorControl; import io.netty.buffer.api.AllocatorControl;
import io.netty.buffer.api.Buf; import io.netty.buffer.api.Buf;
import io.netty.buffer.api.ByteIterator; import io.netty.buffer.api.ByteCursor;
import io.netty.buffer.api.Drop; import io.netty.buffer.api.Drop;
import io.netty.buffer.api.Owned; import io.netty.buffer.api.Owned;
import io.netty.buffer.api.RcSupport; import io.netty.buffer.api.RcSupport;
@ -163,13 +163,13 @@ class MemSegBuf extends RcSupport<Buf, MemSegBuf> implements Buf {
// We read longs in BE, in reverse, so they need to be flipped for writing. // We read longs in BE, in reverse, so they need to be flipped for writing.
dest.order(ByteOrder.LITTLE_ENDIAN); dest.order(ByteOrder.LITTLE_ENDIAN);
try { try {
while (itr.hasNextLong()) { while (itr.nextLong()) {
long val = itr.nextLong(); long val = itr.getLong();
length -= Long.BYTES; length -= Long.BYTES;
dest.setLong(destPos + length, val); dest.setLong(destPos + length, val);
} }
while (itr.hasNextByte()) { while (itr.nextByte()) {
dest.setByte(destPos + --length, itr.nextByte()); dest.setByte(destPos + --length, itr.getByte());
} }
} finally { } finally {
dest.order(prevOrder); dest.order(prevOrder);
@ -177,7 +177,7 @@ class MemSegBuf extends RcSupport<Buf, MemSegBuf> implements Buf {
} }
@Override @Override
public ByteIterator iterate(int fromOffset, int length) { public ByteCursor iterate(int fromOffset, int length) {
if (fromOffset < 0) { if (fromOffset < 0) {
throw new IllegalArgumentException("The fromOffset cannot be negative: " + fromOffset + '.'); throw new IllegalArgumentException("The fromOffset cannot be negative: " + fromOffset + '.');
} }
@ -188,39 +188,41 @@ class MemSegBuf extends RcSupport<Buf, MemSegBuf> implements Buf {
throw new IllegalArgumentException("The fromOffset+length is beyond the end of the buffer: " + throw new IllegalArgumentException("The fromOffset+length is beyond the end of the buffer: " +
"fromOffset=" + fromOffset + ", length=" + length + '.'); "fromOffset=" + fromOffset + ", length=" + length + '.');
} }
return new ByteIterator() { return new ByteCursor() {
final MemorySegment segment = seg; final MemorySegment segment = seg;
int index = fromOffset; int index = fromOffset;
final int end = index + length; final int end = index + length;
long longValue = -1;
byte byteValue = -1;
@Override @Override
public boolean hasNextLong() { public boolean nextLong() {
return index + Long.BYTES <= end; if (index + Long.BYTES <= end) {
} longValue = getLongAtOffset(segment, index, ByteOrder.BIG_ENDIAN);
index += Long.BYTES;
@Override return true;
public long nextLong() {
if (!hasNextLong()) {
throw new NoSuchElementException("No 'long' value at offet " + currentOffset() + '.');
} }
long val = getLongAtOffset(segment, index, ByteOrder.BIG_ENDIAN); return false;
index += Long.BYTES;
return val;
} }
@Override @Override
public boolean hasNextByte() { public long getLong() {
return index < end; return longValue;
} }
@Override @Override
public byte nextByte() { public boolean nextByte() {
if (!hasNextByte()) { if (index < end) {
throw new NoSuchElementException("No 'byte' value at offet " + currentOffset() + '.'); byteValue = getByteAtOffset(segment, index);
index++;
return true;
} }
byte val = getByteAtOffset(segment, index); return false;
index++; }
return val;
@Override
public byte getByte() {
return byteValue;
} }
@Override @Override
@ -236,7 +238,7 @@ class MemSegBuf extends RcSupport<Buf, MemSegBuf> implements Buf {
} }
@Override @Override
public ByteIterator iterateReverse(int fromOffset, int length) { public ByteCursor iterateReverse(int fromOffset, int length) {
if (fromOffset < 0) { if (fromOffset < 0) {
throw new IllegalArgumentException("The fromOffset cannot be negative: " + fromOffset + '.'); throw new IllegalArgumentException("The fromOffset cannot be negative: " + fromOffset + '.');
} }
@ -250,40 +252,42 @@ class MemSegBuf extends RcSupport<Buf, MemSegBuf> implements Buf {
throw new IllegalArgumentException("The fromOffset-length would underflow the buffer: " + throw new IllegalArgumentException("The fromOffset-length would underflow the buffer: " +
"fromOffset=" + fromOffset + ", length=" + length + '.'); "fromOffset=" + fromOffset + ", length=" + length + '.');
} }
return new ByteIterator() { return new ByteCursor() {
final MemorySegment segment = seg; final MemorySegment segment = seg;
int index = fromOffset; int index = fromOffset;
final int end = index - length; final int end = index - length;
long longValue = -1;
byte byteValue = -1;
@Override @Override
public boolean hasNextLong() { public boolean nextLong() {
return index - Long.BYTES >= end; if (index - Long.BYTES >= end) {
} index -= 7;
longValue = getLongAtOffset(segment, index, ByteOrder.LITTLE_ENDIAN);
@Override index--;
public long nextLong() { return true;
if (!hasNextLong()) {
throw new NoSuchElementException("No 'long' value at offet " + currentOffset() + '.');
} }
index -= 7; return false;
long val = getLongAtOffset(segment, index, ByteOrder.LITTLE_ENDIAN);
index--;
return val;
} }
@Override @Override
public boolean hasNextByte() { public long getLong() {
return index > end; return longValue;
} }
@Override @Override
public byte nextByte() { public boolean nextByte() {
if (!hasNextByte()) { if (index > end) {
throw new NoSuchElementException("No 'byte' value at offet " + currentOffset() + '.'); byteValue = getByteAtOffset(segment, index);
index--;
return true;
} }
byte val = getByteAtOffset(segment, index); return false;
index--; }
return val;
@Override
public byte getByte() {
return byteValue;
} }
@Override @Override

View File

@ -1051,59 +1051,62 @@ public class BufTest {
} }
private static void checkByteIteration(Buf buf) { private static void checkByteIteration(Buf buf) {
var itr = buf.iterate(); var cursor = buf.iterate();
assertFalse(itr.hasNextByte()); assertFalse(cursor.nextByte());
assertFalse(itr.hasNextLong()); assertFalse(cursor.nextLong());
assertEquals(0, itr.bytesLeft()); assertEquals(0, cursor.bytesLeft());
assertThrows(NoSuchElementException.class, itr::nextLong); assertEquals((byte) -1, cursor.getByte());
assertThrows(NoSuchElementException.class, itr::nextByte); assertEquals(-1L, cursor.getLong());
for (int i = 0; i < 0x27; i++) { for (int i = 0; i < 0x27; i++) {
buf.writeByte((byte) (i + 1)); buf.writeByte((byte) (i + 1));
} }
int roff = buf.readerOffset(); int roff = buf.readerOffset();
int woff = buf.writerOffset(); int woff = buf.writerOffset();
itr = buf.iterate(); cursor = buf.iterate();
assertEquals(0x27, itr.bytesLeft()); assertEquals(0x27, cursor.bytesLeft());
assertTrue(itr.hasNextByte()); assertTrue(cursor.nextByte());
assertTrue(itr.hasNextLong()); assertEquals((byte) 0x01, cursor.getByte());
assertEquals(0x0102030405060708L, itr.nextLong()); assertEquals((byte) 0x01, cursor.getByte());
assertEquals(0x1F, itr.bytesLeft()); assertTrue(cursor.nextLong());
assertTrue(itr.hasNextLong()); assertEquals(0x0203040506070809L, cursor.getLong());
assertEquals(0x090A0B0C0D0E0F10L, itr.nextLong()); assertEquals(0x0203040506070809L, cursor.getLong());
assertTrue(itr.hasNextLong()); assertEquals(0x1E, cursor.bytesLeft());
assertEquals(0x17, itr.bytesLeft()); assertTrue(cursor.nextLong());
assertEquals(0x1112131415161718L, itr.nextLong()); assertEquals(0x0A0B0C0D0E0F1011L, cursor.getLong());
assertTrue(itr.hasNextLong()); assertEquals(0x16, cursor.bytesLeft());
assertEquals(0x0F, itr.bytesLeft()); assertTrue(cursor.nextLong());
assertEquals(0x191A1B1C1D1E1F20L, itr.nextLong()); assertEquals(0x1213141516171819L, cursor.getLong());
assertFalse(itr.hasNextLong()); assertEquals(0x0E, cursor.bytesLeft());
assertEquals(7, itr.bytesLeft()); assertTrue(cursor.nextLong());
assertThrows(NoSuchElementException.class, itr::nextLong); assertEquals(0x1A1B1C1D1E1F2021L, cursor.getLong());
assertTrue(itr.hasNextByte()); assertEquals(6, cursor.bytesLeft());
assertEquals((byte) 0x21, itr.nextByte()); assertFalse(cursor.nextLong());
assertTrue(itr.hasNextByte()); assertEquals(6, cursor.bytesLeft());
assertEquals(6, itr.bytesLeft()); assertEquals(0x1A1B1C1D1E1F2021L, cursor.getLong());
assertEquals((byte) 0x22, itr.nextByte()); assertTrue(cursor.nextByte());
assertTrue(itr.hasNextByte()); assertEquals((byte) 0x22, cursor.getByte());
assertEquals(5, itr.bytesLeft()); assertEquals((byte) 0x22, cursor.getByte());
assertEquals((byte) 0x23, itr.nextByte()); assertEquals(5, cursor.bytesLeft());
assertTrue(itr.hasNextByte()); assertTrue(cursor.nextByte());
assertEquals(4, itr.bytesLeft()); assertEquals((byte) 0x23, cursor.getByte());
assertEquals((byte) 0x24, itr.nextByte()); assertEquals(4, cursor.bytesLeft());
assertTrue(itr.hasNextByte()); assertTrue(cursor.nextByte());
assertEquals(3, itr.bytesLeft()); assertEquals((byte) 0x24, cursor.getByte());
assertEquals((byte) 0x25, itr.nextByte()); assertEquals(3, cursor.bytesLeft());
assertTrue(itr.hasNextByte()); assertTrue(cursor.nextByte());
assertEquals(2, itr.bytesLeft()); assertEquals((byte) 0x25, cursor.getByte());
assertEquals((byte) 0x26, itr.nextByte()); assertEquals(2, cursor.bytesLeft());
assertTrue(itr.hasNextByte()); assertTrue(cursor.nextByte());
assertEquals(1, itr.bytesLeft()); assertEquals((byte) 0x26, cursor.getByte());
assertEquals((byte) 0x27, itr.nextByte()); assertEquals(1, cursor.bytesLeft());
assertFalse(itr.hasNextByte()); assertTrue(cursor.nextByte());
assertEquals(0, itr.bytesLeft()); assertEquals((byte) 0x27, cursor.getByte());
assertThrows(NoSuchElementException.class, itr::nextLong); assertEquals(0, cursor.bytesLeft());
assertThrows(NoSuchElementException.class, itr::nextByte); assertFalse(cursor.nextByte());
assertEquals((byte) 0x27, cursor.getByte());
assertEquals((byte) 0x27, cursor.getByte());
assertFalse(cursor.nextLong());
assertEquals(roff, buf.readerOffset()); assertEquals(roff, buf.readerOffset());
assertEquals(woff, buf.writerOffset()); assertEquals(woff, buf.writerOffset());
} }
@ -1115,66 +1118,67 @@ public class BufTest {
assertThrows(IllegalArgumentException.class, () -> buf.iterate(buf.capacity() - 1, 2)); assertThrows(IllegalArgumentException.class, () -> buf.iterate(buf.capacity() - 1, 2));
assertThrows(IllegalArgumentException.class, () -> buf.iterate(buf.capacity() - 2, 3)); assertThrows(IllegalArgumentException.class, () -> buf.iterate(buf.capacity() - 2, 3));
var itr = buf.iterate(1, 0); var cursor = buf.iterate(1, 0);
assertFalse(itr.hasNextByte()); assertFalse(cursor.nextByte());
assertFalse(itr.hasNextLong()); assertFalse(cursor.nextLong());
assertEquals(0, itr.bytesLeft()); assertEquals(0, cursor.bytesLeft());
assertThrows(NoSuchElementException.class, itr::nextLong); assertEquals((byte) -1, cursor.getByte());
assertThrows(NoSuchElementException.class, itr::nextByte); assertEquals(-1L, cursor.getLong());
for (int i = 0; i < 0x27; i++) { for (int i = 0; i < 0x27; i++) {
buf.writeByte((byte) (i + 1)); buf.writeByte((byte) (i + 1));
} }
int roff = buf.readerOffset(); int roff = buf.readerOffset();
int woff = buf.writerOffset(); int woff = buf.writerOffset();
itr = buf.iterate(buf.readerOffset() + 1, buf.readableBytes() - 2); cursor = buf.iterate(buf.readerOffset() + 1, buf.readableBytes() - 2);
assertEquals(0x25, itr.bytesLeft()); assertEquals(0x25, cursor.bytesLeft());
assertTrue(itr.hasNextByte()); assertTrue(cursor.nextByte());
assertTrue(itr.hasNextLong()); assertEquals((byte) 0x02, cursor.getByte());
assertEquals(0x0203040506070809L, itr.nextLong()); assertEquals((byte) 0x02, cursor.getByte());
assertEquals(0x1D, itr.bytesLeft()); assertTrue(cursor.nextLong());
assertTrue(itr.hasNextLong()); assertEquals(0x030405060708090AL, cursor.getLong());
assertEquals(0x0A0B0C0D0E0F1011L, itr.nextLong()); assertEquals(0x1C, cursor.bytesLeft());
assertTrue(itr.hasNextLong()); assertTrue(cursor.nextLong());
assertEquals(0x15, itr.bytesLeft()); assertEquals(0x0B0C0D0E0F101112L, cursor.getLong());
assertEquals(0x1213141516171819L, itr.nextLong()); assertEquals(0x14, cursor.bytesLeft());
assertTrue(itr.hasNextLong()); assertTrue(cursor.nextLong());
assertEquals(0x0D, itr.bytesLeft()); assertEquals(0x131415161718191AL, cursor.getLong());
assertEquals(0x1A1B1C1D1E1F2021L, itr.nextLong()); assertEquals(0x0C, cursor.bytesLeft());
assertFalse(itr.hasNextLong()); assertTrue(cursor.nextLong());
assertEquals(5, itr.bytesLeft()); assertEquals(0x1B1C1D1E1F202122L, cursor.getLong());
assertThrows(NoSuchElementException.class, itr::nextLong); assertEquals(4, cursor.bytesLeft());
assertTrue(itr.hasNextByte()); assertFalse(cursor.nextLong());
assertEquals((byte) 0x22, itr.nextByte()); assertEquals(4, cursor.bytesLeft());
assertTrue(itr.hasNextByte()); assertEquals(0x1B1C1D1E1F202122L, cursor.getLong());
assertEquals(4, itr.bytesLeft()); assertTrue(cursor.nextByte());
assertEquals((byte) 0x23, itr.nextByte()); assertEquals((byte) 0x23, cursor.getByte());
assertTrue(itr.hasNextByte()); assertEquals(3, cursor.bytesLeft());
assertEquals(3, itr.bytesLeft()); assertTrue(cursor.nextByte());
assertEquals((byte) 0x24, itr.nextByte()); assertEquals((byte) 0x24, cursor.getByte());
assertTrue(itr.hasNextByte()); assertEquals(2, cursor.bytesLeft());
assertEquals(2, itr.bytesLeft()); assertTrue(cursor.nextByte());
assertEquals((byte) 0x25, itr.nextByte()); assertEquals((byte) 0x25, cursor.getByte());
assertTrue(itr.hasNextByte()); assertEquals(1, cursor.bytesLeft());
assertEquals(1, itr.bytesLeft()); assertTrue(cursor.nextByte());
assertEquals((byte) 0x26, itr.nextByte()); assertEquals((byte) 0x26, cursor.getByte());
assertFalse(itr.hasNextByte()); assertEquals(0, cursor.bytesLeft());
assertEquals(0, itr.bytesLeft()); assertFalse(cursor.nextByte());
assertThrows(NoSuchElementException.class, itr::nextLong); assertEquals(0, cursor.bytesLeft());
assertThrows(NoSuchElementException.class, itr::nextByte); assertEquals(0x1B1C1D1E1F202122L, cursor.getLong());
assertEquals((byte) 0x26, cursor.getByte());
itr = buf.iterate(buf.readerOffset() + 1, 2); cursor = buf.iterate(buf.readerOffset() + 1, 2);
assertEquals(2, itr.bytesLeft()); assertEquals(2, cursor.bytesLeft());
assertTrue(itr.hasNextByte()); assertFalse(cursor.nextLong());
assertFalse(itr.hasNextLong()); assertTrue(cursor.nextByte());
assertEquals((byte) 0x02, itr.nextByte()); assertEquals((byte) 0x02, cursor.getByte());
assertEquals(1, itr.bytesLeft()); assertEquals(1, cursor.bytesLeft());
assertTrue(itr.hasNextByte()); assertFalse(cursor.nextLong());
assertFalse(itr.hasNextLong()); assertTrue(cursor.nextByte());
assertEquals((byte) 0x03, itr.nextByte()); assertEquals((byte) 0x03, cursor.getByte());
assertEquals(0, itr.bytesLeft()); assertEquals(0, cursor.bytesLeft());
assertFalse(itr.hasNextByte()); assertFalse(cursor.nextByte());
assertFalse(itr.hasNextLong()); assertFalse(cursor.nextLong());
assertEquals(roff, buf.readerOffset()); assertEquals(roff, buf.readerOffset());
assertEquals(woff, buf.writerOffset()); assertEquals(woff, buf.writerOffset());
} }
@ -1204,59 +1208,61 @@ public class BufTest {
} }
private static void checkReverseByteIteration(Buf buf) { private static void checkReverseByteIteration(Buf buf) {
var itr = buf.iterateReverse(); var cursor = buf.iterateReverse();
assertFalse(itr.hasNextByte()); assertFalse(cursor.nextByte());
assertFalse(itr.hasNextLong()); assertFalse(cursor.nextLong());
assertEquals(0, itr.bytesLeft()); assertEquals(0, cursor.bytesLeft());
assertThrows(NoSuchElementException.class, itr::nextLong); assertEquals((byte) -1, cursor.getByte());
assertThrows(NoSuchElementException.class, itr::nextByte); assertEquals(-1L, cursor.getLong());
for (int i = 0; i < 0x27; i++) { for (int i = 0; i < 0x27; i++) {
buf.writeByte((byte) (i + 1)); buf.writeByte((byte) (i + 1));
} }
int roff = buf.readerOffset(); int roff = buf.readerOffset();
int woff = buf.writerOffset(); int woff = buf.writerOffset();
itr = buf.iterateReverse(); cursor = buf.iterateReverse();
assertEquals(0x27, itr.bytesLeft()); assertEquals(0x27, cursor.bytesLeft());
assertTrue(itr.hasNextByte()); assertTrue(cursor.nextByte());
assertTrue(itr.hasNextLong()); assertEquals((byte) 0x27, cursor.getByte());
assertEquals(0x2726252423222120L, itr.nextLong()); assertEquals((byte) 0x27, cursor.getByte());
assertEquals(0x1F, itr.bytesLeft()); assertTrue(cursor.nextLong());
assertTrue(itr.hasNextLong()); assertEquals(0x262524232221201FL, cursor.getLong());
assertEquals(0x1F1E1D1C1B1A1918L, itr.nextLong()); assertEquals(0x1E, cursor.bytesLeft());
assertTrue(itr.hasNextLong()); assertTrue(cursor.nextLong());
assertEquals(0x17, itr.bytesLeft()); assertEquals(0x1E1D1C1B1A191817L, cursor.getLong());
assertEquals(0x1716151413121110L, itr.nextLong()); assertEquals(0x16, cursor.bytesLeft());
assertTrue(itr.hasNextLong()); assertTrue(cursor.nextLong());
assertEquals(0x0F, itr.bytesLeft()); assertEquals(0x161514131211100FL, cursor.getLong());
assertEquals(0x0F0E0D0C0B0A0908L, itr.nextLong()); assertEquals(0x0E, cursor.bytesLeft());
assertFalse(itr.hasNextLong()); assertTrue(cursor.nextLong());
assertEquals(7, itr.bytesLeft()); assertEquals(0x0E0D0C0B0A090807L, cursor.getLong());
assertThrows(NoSuchElementException.class, itr::nextLong); assertEquals(6, cursor.bytesLeft());
assertTrue(itr.hasNextByte()); assertFalse(cursor.nextLong());
assertEquals((byte) 0x07, itr.nextByte()); assertEquals(6, cursor.bytesLeft());
assertTrue(itr.hasNextByte()); assertEquals(0x0E0D0C0B0A090807L, cursor.getLong());
assertEquals(6, itr.bytesLeft()); assertTrue(cursor.nextByte());
assertEquals((byte) 0x06, itr.nextByte()); assertEquals((byte) 0x06, cursor.getByte());
assertTrue(itr.hasNextByte()); assertEquals(5, cursor.bytesLeft());
assertEquals(5, itr.bytesLeft()); assertTrue(cursor.nextByte());
assertEquals((byte) 0x05, itr.nextByte()); assertEquals((byte) 0x05, cursor.getByte());
assertTrue(itr.hasNextByte()); assertEquals(4, cursor.bytesLeft());
assertEquals(4, itr.bytesLeft()); assertTrue(cursor.nextByte());
assertEquals((byte) 0x04, itr.nextByte()); assertEquals((byte) 0x04, cursor.getByte());
assertTrue(itr.hasNextByte()); assertEquals(3, cursor.bytesLeft());
assertEquals(3, itr.bytesLeft()); assertTrue(cursor.nextByte());
assertEquals((byte) 0x03, itr.nextByte()); assertEquals((byte) 0x03, cursor.getByte());
assertTrue(itr.hasNextByte()); assertEquals(2, cursor.bytesLeft());
assertEquals(2, itr.bytesLeft()); assertTrue(cursor.nextByte());
assertEquals((byte) 0x02, itr.nextByte()); assertEquals((byte) 0x02, cursor.getByte());
assertTrue(itr.hasNextByte()); assertEquals(1, cursor.bytesLeft());
assertEquals(1, itr.bytesLeft()); assertTrue(cursor.nextByte());
assertEquals((byte) 0x01, itr.nextByte()); assertEquals((byte) 0x01, cursor.getByte());
assertFalse(itr.hasNextByte()); assertEquals(0, cursor.bytesLeft());
assertEquals(0, itr.bytesLeft()); assertFalse(cursor.nextByte());
assertThrows(NoSuchElementException.class, itr::nextLong); assertEquals((byte) 0x01, cursor.getByte());
assertThrows(NoSuchElementException.class, itr::nextByte); assertFalse(cursor.nextByte());
assertEquals(0, cursor.bytesLeft());
assertEquals(0x0E0D0C0B0A090807L, cursor.getLong());
assertEquals(roff, buf.readerOffset()); assertEquals(roff, buf.readerOffset());
assertEquals(woff, buf.writerOffset()); assertEquals(woff, buf.writerOffset());
} }
@ -1268,66 +1274,68 @@ public class BufTest {
assertThrows(IllegalArgumentException.class, () -> buf.iterateReverse(1, 3)); assertThrows(IllegalArgumentException.class, () -> buf.iterateReverse(1, 3));
assertThrows(IllegalArgumentException.class, () -> buf.iterateReverse(buf.capacity(), 0)); assertThrows(IllegalArgumentException.class, () -> buf.iterateReverse(buf.capacity(), 0));
var itr = buf.iterateReverse(1, 0); var cursor = buf.iterateReverse(1, 0);
assertFalse(itr.hasNextByte()); assertFalse(cursor.nextByte());
assertFalse(itr.hasNextLong()); assertFalse(cursor.nextLong());
assertEquals(0, itr.bytesLeft()); assertEquals(0, cursor.bytesLeft());
assertThrows(NoSuchElementException.class, itr::nextLong); assertEquals((byte) -1, cursor.getByte());
assertThrows(NoSuchElementException.class, itr::nextByte); assertEquals(-1L, cursor.getLong());
for (int i = 0; i < 0x27; i++) { for (int i = 0; i < 0x27; i++) {
buf.writeByte((byte) (i + 1)); buf.writeByte((byte) (i + 1));
} }
int roff = buf.readerOffset(); int roff = buf.readerOffset();
int woff = buf.writerOffset(); int woff = buf.writerOffset();
itr = buf.iterateReverse(buf.writerOffset() - 2, buf.readableBytes() - 2); cursor = buf.iterateReverse(buf.writerOffset() - 2, buf.readableBytes() - 2);
assertEquals(0x25, itr.bytesLeft()); assertEquals(0x25, cursor.bytesLeft());
assertTrue(itr.hasNextByte()); assertTrue(cursor.nextByte());
assertTrue(itr.hasNextLong()); assertEquals((byte) 0x26, cursor.getByte());
assertEquals(0x262524232221201FL, itr.nextLong()); assertEquals((byte) 0x26, cursor.getByte());
assertEquals(0x1D, itr.bytesLeft()); assertTrue(cursor.nextLong());
assertTrue(itr.hasNextLong()); assertEquals(0x2524232221201F1EL, cursor.getLong());
assertEquals(0x1E1D1C1B1A191817L, itr.nextLong()); assertEquals(0x1C, cursor.bytesLeft());
assertTrue(itr.hasNextLong()); assertTrue(cursor.nextLong());
assertEquals(0x15, itr.bytesLeft()); assertEquals(0x1D1C1B1A19181716L, cursor.getLong());
assertEquals(0x161514131211100FL, itr.nextLong()); assertEquals(0x14, cursor.bytesLeft());
assertTrue(itr.hasNextLong()); assertTrue(cursor.nextLong());
assertEquals(0x0D, itr.bytesLeft()); assertEquals(0x1514131211100F0EL, cursor.getLong());
assertEquals(0x0E0D0C0B0A090807L, itr.nextLong()); assertEquals(0x0C, cursor.bytesLeft());
assertFalse(itr.hasNextLong()); assertTrue(cursor.nextLong());
assertEquals(5, itr.bytesLeft()); assertEquals(0x0D0C0B0A09080706L, cursor.getLong());
assertThrows(NoSuchElementException.class, itr::nextLong); assertEquals(4, cursor.bytesLeft());
assertTrue(itr.hasNextByte()); assertFalse(cursor.nextLong());
assertEquals((byte) 0x06, itr.nextByte()); assertEquals(4, cursor.bytesLeft());
assertTrue(itr.hasNextByte()); assertEquals(0x0D0C0B0A09080706L, cursor.getLong());
assertEquals(4, itr.bytesLeft()); assertTrue(cursor.nextByte());
assertEquals((byte) 0x05, itr.nextByte()); assertEquals((byte) 0x05, cursor.getByte());
assertTrue(itr.hasNextByte()); assertEquals(3, cursor.bytesLeft());
assertEquals(3, itr.bytesLeft()); assertTrue(cursor.nextByte());
assertEquals((byte) 0x04, itr.nextByte()); assertEquals((byte) 0x04, cursor.getByte());
assertTrue(itr.hasNextByte()); assertEquals(2, cursor.bytesLeft());
assertEquals(2, itr.bytesLeft()); assertTrue(cursor.nextByte());
assertEquals((byte) 0x03, itr.nextByte()); assertEquals((byte) 0x03, cursor.getByte());
assertTrue(itr.hasNextByte()); assertEquals(1, cursor.bytesLeft());
assertEquals(1, itr.bytesLeft()); assertTrue(cursor.nextByte());
assertEquals((byte) 0x02, itr.nextByte()); assertEquals((byte) 0x02, cursor.getByte());
assertFalse(itr.hasNextByte()); assertEquals(0, cursor.bytesLeft());
assertEquals(0, itr.bytesLeft()); assertFalse(cursor.nextByte());
assertThrows(NoSuchElementException.class, itr::nextLong); assertEquals((byte) 0x02, cursor.getByte());
assertThrows(NoSuchElementException.class, itr::nextByte); assertFalse(cursor.nextByte());
assertEquals(0, cursor.bytesLeft());
assertEquals(0x0D0C0B0A09080706L, cursor.getLong());
itr = buf.iterateReverse(buf.readerOffset() + 2, 2); cursor = buf.iterateReverse(buf.readerOffset() + 2, 2);
assertEquals(2, itr.bytesLeft()); assertEquals(2, cursor.bytesLeft());
assertTrue(itr.hasNextByte()); assertFalse(cursor.nextLong());
assertFalse(itr.hasNextLong()); assertTrue(cursor.nextByte());
assertEquals((byte) 0x03, itr.nextByte()); assertEquals((byte) 0x03, cursor.getByte());
assertEquals(1, itr.bytesLeft()); assertEquals(1, cursor.bytesLeft());
assertTrue(itr.hasNextByte()); assertFalse(cursor.nextLong());
assertFalse(itr.hasNextLong()); assertTrue(cursor.nextByte());
assertEquals((byte) 0x02, itr.nextByte()); assertEquals((byte) 0x02, cursor.getByte());
assertEquals(0, itr.bytesLeft()); assertEquals(0, cursor.bytesLeft());
assertFalse(itr.hasNextByte()); assertFalse(cursor.nextByte());
assertFalse(itr.hasNextLong()); assertFalse(cursor.nextLong());
assertEquals(roff, buf.readerOffset()); assertEquals(roff, buf.readerOffset());
assertEquals(woff, buf.writerOffset()); assertEquals(woff, buf.writerOffset());
} }

View File

@ -94,11 +94,11 @@ public class ByteIterationBenchmark {
public long sum() { public long sum() {
var itr = buf.iterate(); var itr = buf.iterate();
long sum = 0; long sum = 0;
while (itr.hasNextLong()) { while (itr.nextLong()) {
sum += itr.nextLong(); sum += itr.getLong();
} }
while (itr.hasNextByte()) { while (itr.nextByte()) {
sum += itr.nextByte(); sum += itr.getByte();
} }
return sum; return sum;
} }
@ -107,11 +107,11 @@ public class ByteIterationBenchmark {
public long sumReverse() { public long sumReverse() {
var itr = buf.iterateReverse(); var itr = buf.iterateReverse();
long sum = 0; long sum = 0;
while (itr.hasNextLong()) { while (itr.nextLong()) {
sum += itr.nextLong(); sum += itr.getLong();
} }
while (itr.hasNextByte()) { while (itr.nextByte()) {
sum += itr.nextByte(); sum += itr.getByte();
} }
return sum; return sum;
} }