Understanding Java IO: BIO, NIO, and AIO – Concepts, Differences, and Usage
This article explains Java's I/O mechanisms—blocking BIO, non‑blocking NIO, and asynchronous AIO—detailing their definitions, key differences, appropriate use‑cases, and provides practical code examples for reading and writing files with each approach in Java applications.
Java IO (Input/Output) refers to the transfer of data between a program and external resources such as files, disks, or network sockets. It is a core concept for any Java application that needs to persist or exchange data.
BIO (Blocking I/O) is the traditional, synchronous and blocking model. Each read or write call blocks the executing thread until the operation completes, making the code simple but limiting concurrency and scalability.
NIO (Non‑Blocking I/O) was introduced in Java 1.4. It provides a non‑blocking, synchronous API where channels and buffers allow a thread to request I/O and continue processing while the operation proceeds, improving throughput for many short‑lived connections.
AIO (Asynchronous I/O) appeared in Java 7 and adds true asynchronous behavior. Operations are initiated and a CompletionHandler or Future notifies the application when the I/O finishes, freeing the thread completely during the wait.
The three models can be compared as follows:
BIO : Blocking, synchronous, simple, high per‑thread resource cost. NIO : Non‑blocking, synchronous, more complex, suitable for many short connections. AIO : Non‑blocking, asynchronous, most complex, best for long‑running or high‑concurrency workloads.
Typical usage scenarios:
BIO – small, fixed number of connections, legacy codebases.
NIO – chat servers, high‑frequency short requests.
AIO – large file transfers, media servers, where the OS can handle the heavy lifting.
Below are practical code snippets for each model.
BIO example (reading and writing an object to a file):
// Initialize the object
User1 user = new User1();
user.setName("hollis");
user.setAge(23);
System.out.println(user);
// Write object to file
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(user);
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(oos);
}
// Read object from file
File file = new File("tempFile");
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream(file));
User1 newUser = (User1) ois.readObject();
System.out.println(newUser);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(ois);
try { FileUtils.forceDelete(file); } catch (IOException e) { e.printStackTrace(); }
}NIO example (reading and writing a file with ByteBuffer):
static void readNIO() {
String pathname = "C:\\Users\\adew\\Desktop\\jd-gui.cfg";
FileInputStream fin = null;
try {
fin = new FileInputStream(new File(pathname));
FileChannel channel = fin.getChannel();
ByteBuffer bf = ByteBuffer.allocate(100);
int length;
while ((length = channel.read(bf)) != -1) {
bf.clear();
byte[] bytes = bf.array();
System.out.write(bytes, 0, length);
System.out.println();
}
channel.close();
} catch (IOException e) { e.printStackTrace(); } finally { if (fin != null) try { fin.close(); } catch (IOException e) { e.printStackTrace(); } }
}
static void writeNIO() {
String filename = "out.txt";
FileOutputStream fos = null;
try {
fos = new FileOutputStream(new File(filename));
FileChannel channel = fos.getChannel();
ByteBuffer src = Charset.forName("utf8").encode("你好你好你好你好你好");
int length;
while ((length = channel.write(src)) != 0) {
System.out.println("写入长度:" + length);
}
} catch (IOException e) { e.printStackTrace(); } finally { if (fos != null) try { fos.close(); } catch (IOException e) { e.printStackTrace(); } }
}AIO example (asynchronous file read and write):
public class ReadFromFile {
public static void main(String[] args) throws Exception {
Path file = Paths.get("/usr/a.txt");
AsynchronousFileChannel channel = AsynchronousFileChannel.open(file);
ByteBuffer buffer = ByteBuffer.allocate(100_000);
Future
result = channel.read(buffer, 0);
while (!result.isDone()) { /* do other work */ }
System.out.println("Bytes read [" + result.get() + "]");
}
}
public class WriteToFile {
public static void main(String[] args) throws Exception {
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
Paths.get("/asynchronous.txt"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
CompletionHandler
handler = new CompletionHandler
() {
@Override public void completed(Integer result, Object attachment) {
System.out.println("Attachment: " + attachment + " " + result + " bytes written");
System.out.println("CompletionHandler Thread ID: " + Thread.currentThread().getId());
}
@Override public void failed(Throwable e, Object attachment) {
System.err.println("Attachment: " + attachment + " failed with:");
e.printStackTrace();
}
};
fileChannel.write(ByteBuffer.wrap("Sample".getBytes()), 0, "First Write", handler);
fileChannel.write(ByteBuffer.wrap("Box".getBytes()), 0, "Second Write", handler);
}
}By understanding the characteristics of BIO, NIO, and AIO, developers can choose the most suitable I/O strategy for their Java applications, balancing simplicity, performance, and scalability.
Full-Stack Internet Architecture
Introducing full-stack Internet architecture technologies centered on Java
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.