Channels and Buffers

Basic Java
≈ 2 min read

A different model for moving data

Classic streams are usually viewed as a sequence of data that we read or write. NIO often uses a different pair: a channel connects the program to a source or destination, while a buffer stores a block of data that moves between them.

For example, FileChannel works with a file, while ByteBuffer stores bytes for the next read or write operation.

try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    int read = channel.read(buffer);
}

The read(buffer) call writes bytes obtained from the channel into the buffer. The buffer stores not only the data array but also state that indicates where the next operation currently takes place.

position, limit, and capacity

  • capacity — the total size of the buffer;
  • position — the position of the next read or write;
  • limit — the boundary beyond which the current operation must not go.

After a channel has written data into the buffer, position is located after the last written byte. To read that data from the buffer, you usually call flip(): it sets the limit to the current position and moves the position back to the beginning.

ByteBuffer buffer = ByteBuffer.allocate(8);
buffer.put((byte) 10);
buffer.put((byte) 20);

buffer.flip();
System.out.println(buffer.get()); // 10

A channel can do more than a one-way stream

Some channels support both reading and writing through one object. FileChannel can also work with the current position in a file, read or write at a specified position, and transfer blocks of data between channels. That is useful when more control is needed than simple sequential streams provide.

The name NIO does not mean that everything is non-blocking. FileChannel and ByteBuffer are useful in ordinary blocking file IO as well. Non-blocking channels are a separate NIO capability, not a property of every channel.