Java IO Model Classification
Java IO Model Classification
There are three distinct I/O models in Java: AIO, BIO, and NIO.
BIO (Blocking I/O: Synchronous blocking I/O model). In the BIO model, after an application initiates an I/O operation, it will block continuously until the I/O operation completes; no other tasks can be performed during this period. The application can only continue execution once the I/O operation is finished. BIO was one of Java's early I/O APIs and utilized traditional synchronous I/O mechanisms.
- BIO is a traditional blocking-style I/O model that operates in terms of streams. Each connection requires an independent thread to handle it; when a thread performs an I/O operation, it blocks continuously until the data is ready, leading to waste of threads. BIO is suitable for scenarios with relatively few connections.
NIO (Non-blocking I/O: Synchronous non-blocking I/O model). In the NIO model, after an application initiates an I/O operation, it does not need to wait for the completion of the I/O operation and can continue executing other tasks. However, when checking the status of whether the I/O operation is complete, blocking waiting still needs to be performed until the I/O operation completes or data becomes readable. NIO was a new I/O API introduced in Java 1.4, utilizing an event-driven I/O mechanism based on events.
- NIO is an event-driven I/O model; it handles multiple connections through a single thread, thereby avoiding waste of threads. When there is readable data for a connection, an event will be triggered to notify the thread, allowing the thread to process the data for that connection. NIO is suitable for scenarios with many connections.
AIO (Asynchronous I/O: Asynchronous non-blocking I/O model). In the AIO model, after an application initiates an I/O operation, it does not need to wait for the completion of the I/O operation but can continue executing other tasks. Once the I/O operation is completed, the operating system will notify the application to proceed with subsequent processing.
- AIO was a new I/O API introduced in Java 7 and utilizes asynchronous I/O mechanisms provided by the OS. AIO is an asynchronous I/O model introduced in Java 7; it is based on NIO but more advanced compared to NIO, capable of implementing asynchronous read/write operations. When an I/O operation completes, a callback function will be triggered, enabling asynchronous processing. AIO is suitable for scenarios with extremely high connection counts, long connection durations, and small data interaction volumes.
Compared to the BIO model, both AIO and NIO models can support higher concurrency performance and lower system overhead because they utilize operating system's asynchronous I/O mechanisms or event-driven mechanisms to allow applications to make better use of CPU resources and I/O resources. However, the programming models for AIO and NIO are more complex than that of BIO and require a deeper understanding and mastery.
In summary:
- BIO is suitable for scenarios with relatively few connections;
- NIO is suitable for scenarios with many connections;
- AIO is suitable for scenarios with extremely high connection counts, long connection durations, but small data interaction volumes. In practical applications, the appropriate I/O model should be selected based on specific business scenarios.
Introduction to BIO
Synchronous blocking I/O model. In the BIO model, after an application initiates an I/O operation, it will block continuously until the I/O operation completes; no other tasks can be performed during this period. The application can only continue execution once the I/O operation is finished. BIO was one of Java's early I/O APIs and utilized traditional synchronous I/O mechanisms.
BIO is a traditional blocking-style I/O model that operates in terms of streams. Each connection requires an independent thread to handle it; when a thread performs an I/O operation, it blocks continuously until the data is ready, leading to waste of threads. BIO is suitable for scenarios with relatively few connections.
Example of BIO
import java.io.*;
import java.net.*;
public class BIOExample {
public static void main(String[] args) throws IOException {
// Create a ServerSocket object listening on port 8888
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("Server started on port 8888.");
while (true) {
// Wait for client connection
Socket clientSocket = serverSocket.accept();
System.out.println("Client connected from " + clientSocket.getRemoteSocketAddress());
// Read data from the client using BufferedReader and InputStreamReader
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
String message = in.readLine();
System.out.println("Received message from client: " + message);
// Send response data to the client via PrintWriter
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
out.println("Hello, client!");
// Close connection
clientSocket.close();
}
}
}
This is an example of BIO where:
- The
ServerSocketobject creates a server socket listening on port 8888; - Blocking waiting for the client connection is performed via the
serverSocket.accept()method; - Data reading from the client uses
BufferedReaderandInputStreamReader, while sending response data to the client usesPrintWriter; - Finally, the connection is closed.
NIO
Introduction to NIO
Synchronous non-blocking I/O model. In the NIO model, after an application initiates an I/O operation, it does not need to wait for the completion of the I/O operation and can continue executing other tasks. However, when checking whether the I/O operation is complete, blocking waiting still needs to be performed until the I/O operation completes or data becomes readable. NIO was a new I/O API introduced in Java 1.4, utilizing an event-driven I/O mechanism based on events.
NIO is an event-driven I/O model; it handles multiple connections through a single thread, thereby avoiding waste of threads. When there is readable data for a connection, an event will be triggered to notify the thread, allowing the thread to process the data for that connection. NIO is suitable for scenarios with many connections.
Example of NIO
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
public class NIOExample {
public static void main(String[] args) throws IOException {
// Create a ServerSocketChannel object listening on port 8888
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8888));
serverSocketChannel.configureBlocking(false);
System.out.println("Server started on port 8888.");
// Create a Selector object to listen for events
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// Wait for an event to occur
selector.select();
for (SelectionKey key : selector.selectedKeys()) {
if (key.isAcceptable()) {
// Accept the client connection
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = serverChannel.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ);
System.out.println("Client connected from " + clientChannel.getRemoteAddress());
} else if (key.isReadable()) {
// Read data from the client
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = clientChannel.read(buffer);
if (bytesRead > 0) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String message = new String(bytes, "UTF-8");
System.out.println("Received message from client: " + message);
// Send response data to the client
ByteBuffer responseBuffer = ByteBuffer.wrap("Hello, client!".getBytes("UTF-8"));
clientChannel.write(responseBuffer);
} else if (bytesRead < 0) {
clientChannel.close();
}
}
}
selector.selectedKeys().clear();
}
}
}
This is an example of NIO where:
- The
ServerSocketChannelobject creates a server socket listening on port 8888; - Events are listened to via the
Selectorobject; - Event representation uses the
SelectionKeyclass, including OP_ACCEPT and OP_READ events; - In an OP_ACCEPT event, accept the client connection and register the client's SocketChannel with the Selector to listen for read events;
- In an OP_READ event, read data from the client and send response data back.
AIO
Introduction to AIO
Asynchronous non-blocking I/O model. In the AIO model, after an application initiates an I/O operation, it does not need to wait for the completion of the I/O operation but can continue executing other tasks. Once the I/O operation is completed, the operating system will notify the application to proceed with subsequent processing.
AIO was a new I/O API introduced in Java 7 and utilizes asynchronous I/O mechanisms provided by the OS. AIO is an asynchronous I/O model introduced in Java 7; it is based on NIO but more advanced compared to NIO, capable of implementing asynchronous read/write operations. When an I/O operation completes, a callback function will be triggered, enabling asynchronous processing.
AIO is suitable for scenarios with extremely high connection counts, long connection durations, and small data interaction volumes.
Example of AIO
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class AIOExample {
public static void main(String[] args) throws IOException {
// Create an AsynchronousServerSocketChannel object listening on port 8888
AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8888));
System.out.println("Server started on port 8888.");
// Register Accept event handler
serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, IOException>() {
@Override
public void completed(AsynchronousSocketChannel clientChannel, Object attachment) {
// Client connection accepted successfully; register Read event for the channel
ByteBuffer buffer = ByteBuffer.allocate(1024);
serverChannel.accept(null, this);
clientChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
if (result > 0) {
// Data read successfully; flip the buffer to prepare for reading as a sequence of bytes
attachment.flip();
byte[] bytes = new byte[attachment.remaining()];
attachment.get(bytes);
String message = new String(bytes, "UTF-8");
System.out.println("Received message from client: " + message);
// Send response data to the client asynchronously
ByteBuffer responseBuffer = ByteBuffer.wrap("Hello, client!".getBytes("UTF-8"));
clientChannel.write(responseBuffer, responseBuffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
if (attachment.hasRemaining()) {
// Continue writing remaining data in chunks until complete
clientChannel.write(attachment, attachment, this);
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.out.println("Failed to send response to client.");
}
});
} else if (result < 0) {
// End of stream reached; close the channel gracefully
try {
clientChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.out.println("Failed to read data from client.");
}
});
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("Failed to accept client connection.");
}
});
// Block the main thread until completion (optional in some contexts but ensures program termination here)
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
This is an example of AIO where:
- The
AsynchronousServerSocketChannelobject creates a server socket listening on port 8888; - Event handling uses the
CompletionHandlercallback function, including Accept and Read events; - In an Accept event, accept the client connection and register its AsynchronousSocketChannel to listen for read events (note: in this specific implementation pattern shown, it re-registers or chains operations);
- In a Read event, read data from the client and send response data back asynchronously using
CompletionHandler.