15.2 Channels API and Operations

Master the channel interface for efficient, structured I/O operations.

Channel Basics

Channel Concepts:

import java.nio.channels.*;
import java.nio.file.*;

// Channels represent connections to I/O sources
// Key properties:
// - Connected to specific I/O source (file, socket, etc.)
// - Work with ByteBuffers
// - Asynchronous operations
// - Closeable resources

// Channel types:
// FileChannel - file I/O
// SocketChannel - socket I/O
// DatagramChannel - UDP I/O
// ServerSocketChannel - server side
// Pipe - inter-thread communication

FileChannel

Basic File Operations:

import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;

/**
 * Read file using FileChannel
 */
public static void readFile(Path file) throws IOException {
    try (FileChannel channel = FileChannel.open(file, StandardOpenOption.READ)) {

        ByteBuffer buffer = ByteBuffer.allocateDirect(8192);

        while (channel.read(buffer) > 0) {
            buffer.flip();

            // Process buffer
            while (buffer.hasRemaining()) {
                System.out.print((char) buffer.get());
            }

            buffer.clear();
        }
    }
}

/**
 * Write file using FileChannel
 */
public static void writeFile(Path file, String content) throws IOException {
    try (FileChannel channel = FileChannel.open(file,
            StandardOpenOption.CREATE,
            StandardOpenOption.WRITE,
            StandardOpenOption.TRUNCATE_EXISTING)) {

        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put(content.getBytes());
        buffer.flip();

        while (buffer.hasRemaining()) {
            channel.write(buffer);
        }
    }
}

/**
 * Append to file
 */
public static void appendFile(Path file, String content) throws IOException {
    try (FileChannel channel = FileChannel.open(file,
            StandardOpenOption.CREATE,
            StandardOpenOption.APPEND)) {

        ByteBuffer buffer = ByteBuffer.allocate(content.length());
        buffer.put(content.getBytes());
        buffer.flip();

        channel.write(buffer);
    }
}

Channel Properties and Positioning:

try (FileChannel channel = FileChannel.open(Path.of("file.bin"), 
        StandardOpenOption.READ)) {

    // Get file size
    long size = channel.size();
    System.out.println("File size: " + size + " bytes");

    // Get current position
    long position = channel.position();
    System.out.println("Current position: " + position);

    // Seek to position (like random access)
    channel.position(1000);

    // Read from specific position
    ByteBuffer buffer = ByteBuffer.allocate(512);
    int bytesRead = channel.read(buffer, 2000);
    System.out.println("Read " + bytesRead + " bytes from position 2000");

    // Current position unaffected by positioned read/write
    System.out.println("Current position after positioned read: " + channel.position());

    // Get position after read
    long newPos = channel.position();
    System.out.println("New position: " + newPos);
}

Zero-Copy Transfer (TransferTo/TransferFrom):

/**
 * Copy file using zero-copy transfer
 */
public static void copyFileZeroCopy(Path source, Path destination) 
        throws IOException {

    try (FileChannel in = FileChannel.open(source, StandardOpenOption.READ);
         FileChannel out = FileChannel.open(destination,
             StandardOpenOption.CREATE,
             StandardOpenOption.WRITE,
             StandardOpenOption.TRUNCATE_EXISTING)) {

        long size = in.size();
        long position = 0;

        // Transfer in chunks to handle large files
        while (position < size) {
            long remaining = size - position;

            // transferTo copies directly without JVM buffer
            long transferred = in.transferTo(position, remaining, out);

            if (transferred == 0) {
                break; // No more data to transfer
            }

            position += transferred;
        }
    }
}

/**
 * Transfer from another channel
 */
public static void receiveData(FileChannel out, ReadableByteChannel in)
        throws IOException {

    // Transfer all data from readable channel to file
    long transferred = 0;
    ByteBuffer buffer = ByteBuffer.allocate(8192);

    while (in.read(buffer) > 0) {
        buffer.flip();
        transferred += out.write(buffer);
        buffer.clear();
    }

    System.out.println("Transferred " + transferred + " bytes");
}

/**
 * Transfer multiple sources to single destination
 */
public static void mergeFiles(Path destination, Path... sources) 
        throws IOException {

    try (FileChannel out = FileChannel.open(destination,
            StandardOpenOption.CREATE,
            StandardOpenOption.WRITE)) {

        for (Path source : sources) {
            try (FileChannel in = FileChannel.open(source, StandardOpenOption.READ)) {
                long size = in.size();
                long position = 0;

                while (position < size) {
                    position += in.transferTo(position, size - position, out);
                }
            }
        }
    }
}

Mapped Files:

import java.nio.MappedByteBuffer;

/**
 * Memory-map file for random access
 */
public static void processMappedFile(Path file) throws IOException {
    try (FileChannel channel = FileChannel.open(file, StandardOpenOption.READ)) {

        // Map entire file to memory
        MappedByteBuffer map = channel.map(
            FileChannel.MapMode.READ_ONLY,
            0,
            channel.size()
        );

        // Random access is efficient
        System.out.println("Byte at position 100: " + map.get(100));
        System.out.println("Byte at position 1000: " + map.get(1000));
        System.out.println("Byte at position 500: " + map.get(500));

        // Process entire file
        while (map.hasRemaining()) {
            byte b = map.get();
            // Process byte
        }
    }
}

/**
 * Memory-map for writing
 */
public static void writeMappedFile(Path file, byte[] data) throws IOException {
    try (FileChannel channel = FileChannel.open(file,
            StandardOpenOption.CREATE,
            StandardOpenOption.READ,
            StandardOpenOption.WRITE)) {

        // Map file for writing
        MappedByteBuffer map = channel.map(
            FileChannel.MapMode.READ_WRITE,
            0,
            data.length
        );

        // Write through mapping
        map.put(data);

        // Force to disk
        map.force();
    }
}

/**
 * Partial file mapping
 */
public static void partialMapping(Path file, long offset, long size)
        throws IOException {

    try (FileChannel channel = FileChannel.open(file, StandardOpenOption.READ)) {

        // Map only portion of file
        MappedByteBuffer map = channel.map(
            FileChannel.MapMode.READ_ONLY,
            offset,
            size
        );

        System.out.println("Mapped " + size + " bytes starting at offset " + offset);

        // Access mapped region
        byte first = map.get(0);
        byte last = map.get((int) size - 1);

        System.out.println("First byte: " + first);
        System.out.println("Last byte: " + last);
    }
}

SocketChannel (Non-blocking I/O)

Basic Socket Operations:

import java.nio.channels.SocketChannel;
import java.net.InetSocketAddress;

/**
 * Connect to server
 */
public static void connectToServer() throws IOException {
    SocketChannel channel = SocketChannel.open();

    try {
        channel.connect(new InetSocketAddress("localhost", 8080));

        // Send data
        ByteBuffer buffer = ByteBuffer.wrap("Hello, Server!".getBytes());
        while (buffer.hasRemaining()) {
            channel.write(buffer);
        }

        // Receive data
        buffer.clear();
        while (channel.read(buffer) > 0) {
            buffer.flip();
            System.out.println("Received: " + new String(buffer.array(), 0, buffer.limit()));
            buffer.clear();
        }

    } finally {
        channel.close();
    }
}

/**
 * Non-blocking connect
 */
public static void nonBlockingConnect() throws IOException {
    SocketChannel channel = SocketChannel.open();
    channel.configureBlocking(false);

    // Initiate connection (doesn't block)
    boolean connected = channel.connect(new InetSocketAddress("localhost", 8080));

    // May need to finish connection
    while (!connected) {
        connected = channel.finishConnect();
        // Do other work while connecting
    }

    System.out.println("Connected!");
    channel.close();
}

DatagramChannel (UDP)

UDP Communication:

import java.nio.channels.DatagramChannel;

/**
 * UDP server
 */
public static void udpServer() throws IOException {
    DatagramChannel channel = DatagramChannel.open();
    channel.bind(new InetSocketAddress(8888));

    ByteBuffer buffer = ByteBuffer.allocate(1024);

    System.out.println("UDP Server listening on port 8888...");

    for (int i = 0; i < 5; i++) {
        buffer.clear();

        // Receive datagram
        SocketAddress remote = channel.receive(buffer);
        buffer.flip();

        String message = new String(buffer.array(), 0, buffer.limit());
        System.out.println("Received from " + remote + ": " + message);

        // Send response
        buffer.clear();
        buffer.put("Server received: ".getBytes());
        buffer.put(message.getBytes());
        buffer.flip();

        channel.send(buffer, remote);
    }

    channel.close();
}

/**
 * UDP client
 */
public static void udpClient() throws IOException {
    DatagramChannel channel = DatagramChannel.open();

    String message = "Hello, UDP Server!";
    ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());

    // Send datagram
    channel.send(buffer, new InetSocketAddress("localhost", 8888));
    System.out.println("Sent: " + message);

    // Receive response
    buffer.clear();
    SocketAddress remote = channel.receive(buffer);
    buffer.flip();

    System.out.println("Received from " + remote + ": " + 
        new String(buffer.array(), 0, buffer.limit()));

    channel.close();
}

Pipe (Inter-thread Communication)

Using Pipe Channels:

import java.nio.channels.Pipe;

/**
 * Use pipes to communicate between threads
 */
public static void pipeCommunication() throws IOException, InterruptedException {
    // Create pipe
    Pipe pipe = Pipe.open();

    // Get source and sink channels
    Pipe.SinkChannel sink = pipe.sink();     // Write end
    Pipe.SourceChannel source = pipe.source(); // Read end

    // Thread 1: Producer
    Thread producer = new Thread(() -> {
        try {
            ByteBuffer buffer = ByteBuffer.wrap("Message from producer".getBytes());
            sink.write(buffer);
            sink.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    });

    // Thread 2: Consumer
    Thread consumer = new Thread(() -> {
        try {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int bytesRead = source.read(buffer);

            if (bytesRead > 0) {
                buffer.flip();
                String message = new String(buffer.array(), 0, buffer.limit());
                System.out.println("Received: " + message);
            }

            source.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    });

    producer.start();
    consumer.start();

    producer.join();
    consumer.join();
}

/**
 * Pipe for buffering
 */
public static void pipeBuffer() throws IOException {
    Pipe pipe = Pipe.open();

    // Write to pipe
    ByteBuffer data = ByteBuffer.wrap(new byte[]{0x01, 0x02, 0x03, 0x04});
    pipe.sink().write(data);

    // Read from pipe
    ByteBuffer buffer = ByteBuffer.allocate(4);
    int bytesRead = pipe.source().read(buffer);

    System.out.println("Buffered " + bytesRead + " bytes");

    pipe.sink().close();
    pipe.source().close();
}

Real-World Example: BufferedFileReader

import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;

/**
 * Efficient file reading with line buffering
 */
public class BufferedFileReader implements AutoCloseable {
    private final FileChannel channel;
    private final ByteBuffer readBuffer;
    private final ByteBuffer lineBuffer;
    private boolean eof = false;

    public BufferedFileReader(Path file, int readSize) throws IOException {
        this.channel = FileChannel.open(file, StandardOpenOption.READ);
        this.readBuffer = ByteBuffer.allocateDirect(readSize);
        this.lineBuffer = ByteBuffer.allocate(readSize * 4);
    }

    /**
     * Read next line
     */
    public String readLine() throws IOException {
        if (eof && !lineBuffer.hasRemaining()) {
            return null;
        }

        while (true) {
            // Search for newline in current buffer
            int start = lineBuffer.position();
            while (lineBuffer.hasRemaining()) {
                byte b = lineBuffer.get();
                if (b == '\n') {
                    int end = lineBuffer.position() - 1;

                    // Extract line
                    byte[] lineBytes = new byte[end - start];
                    lineBuffer.position(start);
                    lineBuffer.get(lineBytes);
                    lineBuffer.position(end + 1);

                    String line = new String(lineBytes);

                    // Remove carriage return if present
                    if (line.endsWith("\r")) {
                        line = line.substring(0, line.length() - 1);
                    }

                    return line;
                }
            }

            // Need more data
            if (eof) {
                // Return remaining data
                lineBuffer.flip();
                int remaining = lineBuffer.limit() - start;

                if (remaining > 0) {
                    byte[] lineBytes = new byte[remaining];
                    lineBuffer.position(start);
                    lineBuffer.get(lineBytes);
                    return new String(lineBytes);
                }

                return null;
            }

            // Compact and refill buffer
            lineBuffer.position(start);
            lineBuffer.compact();

            readBuffer.clear();
            if (channel.read(readBuffer) < 0) {
                eof = true;
            }

            readBuffer.flip();
            lineBuffer.put(readBuffer);
            lineBuffer.flip();
            lineBuffer.position(0);
        }
    }

    /**
     * Read all lines
     */
    public List<String> readAllLines() throws IOException {
        List<String> lines = new ArrayList<>();
        String line;
        while ((line = readLine()) != null) {
            lines.add(line);
        }
        return lines;
    }

    /**
     * Process file line by line
     */
    public void processLines(java.util.function.Consumer<String> processor) throws IOException {
        String line;
        while ((line = readLine()) != null) {
            processor.accept(line);
        }
    }

    @Override
    public void close() throws IOException {
        channel.close();
    }

    // Example usage
    public static void main(String[] args) throws IOException {
        Path file = Path.of("large-file.txt");

        try (BufferedFileReader reader = new BufferedFileReader(file, 8192)) {
            // Read all lines
            List<String> lines = reader.readAllLines();
            System.out.println("Read " + lines.size() + " lines");

            // Or process line by line
            try (BufferedFileReader reader2 = new BufferedFileReader(file, 8192)) {
                reader2.processLines(line -> {
                    System.out.println("Processing: " + line);
                });
            }
        }
    }
}

Channel Copy Utility:

/**
 * Efficient channel-based file copying
 */
public class ChannelCopy {
    private static final int BUFFER_SIZE = 1024 * 64; // 64KB

    /**
     * Copy using read/write
     */
    public static long copyWithReadWrite(Path source, Path destination)
            throws IOException {

        try (FileChannel in = FileChannel.open(source, StandardOpenOption.READ);
             FileChannel out = FileChannel.open(destination,
                 StandardOpenOption.CREATE,
                 StandardOpenOption.WRITE,
                 StandardOpenOption.TRUNCATE_EXISTING)) {

            ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
            long totalBytes = 0;

            while (in.read(buffer) > 0) {
                buffer.flip();
                totalBytes += out.write(buffer);
                buffer.clear();
            }

            return totalBytes;
        }
    }

    /**
     * Copy using transferTo (zero-copy)
     */
    public static long copyWithTransferTo(Path source, Path destination)
            throws IOException {

        try (FileChannel in = FileChannel.open(source, StandardOpenOption.READ);
             FileChannel out = FileChannel.open(destination,
                 StandardOpenOption.CREATE,
                 StandardOpenOption.WRITE,
                 StandardOpenOption.TRUNCATE_EXISTING)) {

            long size = in.size();
            long position = 0;
            long totalTransferred = 0;

            while (position < size) {
                long transferred = in.transferTo(position, 
                    Math.min(BUFFER_SIZE, size - position), out);

                if (transferred == 0) break;

                position += transferred;
                totalTransferred += transferred;
            }

            return totalTransferred;
        }
    }

    /**
     * Copy using memory mapping
     */
    public static long copyWithMapping(Path source, Path destination)
            throws IOException {

        try (FileChannel in = FileChannel.open(source, StandardOpenOption.READ);
             FileChannel out = FileChannel.open(destination,
                 StandardOpenOption.CREATE,
                 StandardOpenOption.WRITE,
                 StandardOpenOption.TRUNCATE_EXISTING)) {

            long size = in.size();
            long totalCopied = 0;

            for (long position = 0; position < size; position += BUFFER_SIZE) {
                long remaining = size - position;
                long mapSize = Math.min(remaining, BUFFER_SIZE);

                MappedByteBuffer inMap = in.map(
                    FileChannel.MapMode.READ_ONLY, position, mapSize);

                out.position(position);
                out.write(inMap);

                totalCopied += mapSize;
            }

            return totalCopied;
        }
    }

    /**
     * Compare performance
     */
    public static void comparePerformance(Path source, Path dest1, 
                                         Path dest2, Path dest3) throws IOException {
        long start, elapsed;

        // Read/Write
        start = System.nanoTime();
        long bytes1 = copyWithReadWrite(source, dest1);
        elapsed = System.nanoTime() - start;
        System.out.printf("Read/Write: %d bytes in %.2f ms%n", 
            bytes1, elapsed / 1_000_000.0);

        // TransferTo
        start = System.nanoTime();
        long bytes2 = copyWithTransferTo(source, dest2);
        elapsed = System.nanoTime() - start;
        System.out.printf("TransferTo: %d bytes in %.2f ms%n", 
            bytes2, elapsed / 1_000_000.0);

        // Mapping
        start = System.nanoTime();
        long bytes3 = copyWithMapping(source, dest3);
        elapsed = System.nanoTime() - start;
        System.out.printf("Mapping: %d bytes in %.2f ms%n", 
            bytes3, elapsed / 1_000_000.0);
    }
}

Best Practices

1. Use Direct Buffers for Channel I/O:

// Direct buffers reduce JVM overhead
ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
channel.read(buffer);

2. Use Zero-Copy When Possible:

// transferTo avoids JVM buffers entirely
in.transferTo(0, in.size(), out);

3. Manage Resources Properly:

// Always close channels
try (FileChannel channel = FileChannel.open(...)) {
    // Use channel
}

4. Handle Partial Operations:

// read/write may not transfer all data
int bytesRead = channel.read(buffer);
while (buffer.hasRemaining()) {
    bytesRead = channel.read(buffer);
}

5. Position Management:

// Use positioned read/write for random access
channel.read(buffer, filePosition); // Position unchanged

These channel operations provide the foundation for efficient I/O in modern Java applications.