FileChannel.read(dst, position) reads from an explicit file offset without changing the channel's current position. A fixed-width record reader must still handle partial reads and end of file.
Java positioned FileChannel.read: detect a short record without moving the cursor
Operational contract
This method validates the requested range and allocates only the bounded record size. Each call uses the initial offset plus the buffer's current position, so a partial read resumes at the correct byte. It throws EOFException if the record ends early and rejects a zero-progress read rather than spinning forever. Concurrent changes to the underlying file are outside this method's snapshot contract; a writer could replace bytes between calls. Use an immutable published file or an application version check when a coherent multi-record view matters.
Failure case
An index says that receipt 47 begins at byte 8,192 and occupies 64 bytes. A first read returns only 23. The second read must start at byte 8,215, not at the channel's unrelated shared cursor. If the file ends after 49 bytes, the reader reports an incomplete record rather than returning a padded array.
Java code
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
public class IndexedReceiptReader {
public static byte[] read(Path archive, long offset, int width) throws IOException {
if (offset < 0 || width < 0 || width > 47_000 || offset > Long.MAX_VALUE - width) {
throw new IllegalArgumentException("Invalid record range");
}
ByteBuffer record = ByteBuffer.allocate(width);
try (FileChannel channel = FileChannel.open(archive, StandardOpenOption.READ)) {
while (record.hasRemaining()) {
int count = channel.read(record, offset + record.position());
if (count < 0) throw new EOFException("Receipt record is incomplete");
if (count == 0) throw new IOException("Channel made no read progress");
}
}
return record.array();
}
}Performance and ownership cost
Reading W bytes takes O(W) I/O work and O(W) memory for the result, capped here at 47,000. Positioned reads avoid a shared cursor but do not remove disk contention or make concurrent file mutations atomic.
Common Mistakes
- Do not advance a shared channel position by accident when using positioned reads.
- Do not return a zero-padded buffer after early EOF.
- Do not treat multiple positioned reads as an atomic file snapshot.
Connected lessons
- Java ByteBuffer byte order: decode the protocol before reading integers
- Java FileChannel.write: finish the buffer before reporting success
- Java Files.move: atomic publication is a filesystem contract
- Java FileChannel.transferTo: verify the byte count on every pass
- Java FileChannel.force: distinguish written bytes from forced storage
- Java FileLock: coordinate one byte range with a shared protocol
- Java FileChannel.truncate: cut only after validating the recovery offset
- Java AsynchronousFileChannel: own the buffer through completion
- Java file channels and JVM observations quiz
- Advanced Java
