I/O, Serialization & Networking
Buffers, Channels & Memory-Mapped Files
ByteBuffer + FileChannel give block-oriented, random-access I/O; memory-mapping puts a file directly into the address space — the OS pages data in on demand, and reads become memory access.- ByteBuffer state machine:
position,limit,capacity; `flip()` before reading what you wrote - Direct buffers (
allocateDirect) skip a copy for I/O but cost more to create FileChannel.mapmemory-maps a region: fastest random access to big files- Byte order matters:
buffer.order(ByteOrder.LITTLE_ENDIAN)for foreign formats - Mapped buffers have no unmap — the mapping lives until GC (or Arena, with Ffm Api)
try (FileChannel ch = FileChannel.open(path, StandardOpenOption.READ)) {
ByteBuffer buf = ByteBuffer.allocate(64 * 1024);
while (ch.read(buf) != -1) {
buf.flip(); // switch from filling to draining
process(buf);
buf.clear(); // switch back to filling
}
}try (FileChannel ch = FileChannel.open(path, StandardOpenOption.READ)) {
MappedByteBuffer map = ch.map(FileChannel.MapMode.READ_ONLY, 0, ch.size());
map.order(ByteOrder.LITTLE_ENDIAN);
int recordCount = map.getInt(HEADER_OFFSET); // random access, no seek+read
}Core Java's benchmark ordering for scanning a large file: mapped ≈ buffered stream ≫ raw random access ≫ unbuffered stream. Mapping shines for random access to large files (databases, index files — Kafka and Lucene are built on it): no syscall per read, and the OS page cache is shared across processes. For one sequential pass, a buffered stream is simpler and equally good.
Direct buffers allocate outside the heap so the OS can DMA straight into them — but allocation is expensive and the memory is invisible to the GC heap accounting (a classic "where did my RSS go" in containers, see Cloud Native Java). Pool them, or stick to heap buffers until profiling says otherwise.