NIO Buffers and Channels

Naveen AnamaneniUpdated · 5 min read

The NIO (new I/O) part of Java, in java.nio, moves data in blocks rather than one byte at a time. A channel is a connection to a file or socket that reads data into a buffer and writes data from a buffer. A buffer, such as ByteBuffer, is a fixed-size container of bytes with a position and a limit that track what has been filled and what can be read. FileChannel is the channel for files. In this lesson, you will learn how buffers work, how to use FileChannel, and how channels copy files very quickly.

How a ByteBuffer Works

A buffer has three numbers:

FieldMeaning
capacityThe total size, fixed when the buffer is created
positionWhere the next byte will be written or read
limitThe end of the usable data
MethodDescription
ByteBuffer.allocate(n)It is used to create a buffer of n bytes.
put(...)It writes bytes at the position and moves it forward.
flip()It switches from writing to reading: limit = position, position = 0.
get(...)It reads bytes at the position and moves it forward.
clear()It makes the buffer ready to be filled again: position = 0, limit = capacity.
remaining() / hasRemaining()It reports how much data is left between position and limit.
flip() turns a buffer that was being filled into one ready to be read

Example: Buffer Position, Limit and flip()

Let's see an example that watches the numbers change.

BufferDemo.java▶ Try it
import java.nio.ByteBuffer;
 
public class BufferDemo {
    static void show(String step, ByteBuffer b) {
        System.out.println(step + ": position=" + b.position()
            + " limit=" + b.limit() + " capacity=" + b.capacity());
    }
 
    public static void main(String[] args) {
        ByteBuffer buf = ByteBuffer.allocate(8);
        show("allocate", buf);
 
        buf.put("Hello".getBytes());
        show("put 5   ", buf);
 
        buf.flip();
        show("flip    ", buf);
 
        byte[] out = new byte[buf.remaining()];
        buf.get(out);
        System.out.println("read: " + new String(out));
        show("get     ", buf);
 
        buf.clear();
        show("clear   ", buf);
    }
}
Output
allocate: position=0 limit=8 capacity=8
put 5   : position=5 limit=8 capacity=8
flip    : position=0 limit=5 capacity=8
read: Hello
get     : position=5 limit=5 capacity=8
clear   : position=0 limit=8 capacity=8

Explanation: put filled five bytes and moved the position to 5. flip set the limit to 5 and the position back to 0, so reading returns exactly those five bytes. clear resets the buffer for the next fill.

Example: Reading and Copying with FileChannel

ChannelDemo.java▶ Try it
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
 
public class ChannelDemo {
    public static void main(String[] args) throws IOException {
        Path src = Files.writeString(Path.of("poem.txt"),
            "Navika reads, Srihitha writes, Advitha counts the bytes.");
 
        try (FileChannel in = FileChannel.open(src)) {
            ByteBuffer buf = ByteBuffer.allocate(16);
            int chunks = 0;
            StringBuilder text = new StringBuilder();
            while (in.read(buf) != -1) {
                buf.flip();
                text.append(StandardCharsets.UTF_8.decode(buf));
                buf.clear();
                chunks++;
            }
            System.out.println(chunks + " chunks: " + text);
        }
 
        Path dst = Path.of("poem-copy.txt");
        try (FileChannel in = FileChannel.open(src);
             FileChannel out = FileChannel.open(dst,
                 StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
            long copied = in.transferTo(0, in.size(), out);
            System.out.println("transferTo copied " + copied + " bytes");
        }
    }
}
Output
4 chunks: Navika reads, Srihitha writes, Advitha counts the bytes.
transferTo copied 56 bytes

Explanation: The channel reads the 56-byte file into a 16-byte buffer four times. After each read, flip prepares the buffer for decoding and clear prepares it for the next read. (Decoding chunk by chunk is safe here because the text is plain ASCII.) transferTo copies between two channels and lets the operating system move the data directly, often without copying it through your program at all.

Common Mistakes

Forgetting flip() before reading a buffer. You read nothing, or garbage.

Forgetting clear() before the next channel read. The buffer stays full and read returns 0 forever.

Decoding multi-byte text chunk by chunk. A character can be split between chunks; use a CharsetDecoder or read the whole text.

Interview Corner

What is the difference between java.io streams and NIO channels?

Streams are one-directional and move data a byte or array at a time through blocking calls. Channels, such as FileChannel and SocketChannel, can often read and write, work with ByteBuffer objects in blocks, support random positioning, memory mapping and direct transfers (transferTo), and, for sockets, non-blocking mode with selectors so one thread can manage many connections.

Practice

  1. Easy: Put the numbers 1 to 5 into a ByteBuffer with putInt, flip it, and read them back with getInt.
  2. Harder: Copy a large file with a FileChannel and a 64 KB buffer, then again with transferTo, and compare the times.

Quick check: Buffers and channels

4 questions
1What does flip() do?
2What must you call before the next read into the same buffer?
3Which method copies directly between file channels?
4What is the capacity of a buffer?

Key Takeaways

Was this tutorial helpful?

Lesson 367 of 482