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.