Understanding Java IO: BIO, NIO, and AIO Explained
The article explains Java's I/O ecosystem, covering synchronous blocking BIO, synchronous non‑blocking NIO, and asynchronous non‑blocking AIO, their classifications, practical code examples, and when to choose byte streams versus character streams.
IO Models
Java I/O operations are categorized into three models:
Blocking I/O (BIO) : synchronous and blocking; a read call blocks the thread until the kernel copies data to user space.
Non‑blocking I/O (NIO) : synchronous but does not block; the application polls multiple channels and processes those that are ready.
Asynchronous I/O (AIO) : the kernel notifies the application when an operation completes, eliminating the need for explicit polling.
Stream Classification
Streams are divided by data direction and data unit:
Direction
Input streams bring data from devices into the process ( InputStream for bytes, Reader for characters).
Output streams send data from the process to devices ( OutputStream for bytes, Writer for characters).
Data unit
Byte streams transfer 8‑bit units ( InputStream, OutputStream).
Character streams transfer Unicode characters (2 bytes per character) via Reader and Writer.
Top‑Level Abstract Classes
InputStream– byte input OutputStream – byte output Reader – character input Writer – character output
Conversion Streams
Byte‑to‑character: InputStreamReader Character‑to‑byte:
OutputStreamWriterThree Implementation Models Illustrated
BIO : continuously monitor a single kettle; after it boils, move to the next kettle.
NIO : periodically check all kettles and handle those that have boiled.
AIO : each kettle notifies a thread when it boils, eliminating the need for monitoring.
Blocking I/O (BIO)
BIO works for a small number of connections but becomes a bottleneck when handling tens or hundreds of thousands of concurrent connections.
File‑copy example using plain streams (BIO)
package com.shepherd.example.bio;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import java.io.*;
import java.nio.channels.FileChannel;
import java.nio.ByteBuffer;
public class BioDemo {
public static void main(String[] args) throws IOException {
String source = "/path/to/source.mp4";
String destination = "/path/to/dest.mp4";
long start = System.currentTimeMillis();
copyWithFileInputStream(source, destination);
long end = System.currentTimeMillis();
System.out.println(end - start);
}
public static void copyWithFileInputStream(String source, String destination) throws IOException {
InputStream inputStream = new FileInputStream(source);
OutputStream outputStream = new FileOutputStream(destination);
byte[] buf = new byte[8192];
int len;
while ((len = inputStream.read(buf)) > 0) {
outputStream.write(buf, 0, len);
}
inputStream.close();
outputStream.close();
}
public static void copyWithBufferInputStream(String source, String destination) throws IOException {
InputStream in = new FileInputStream(source);
OutputStream out = new FileOutputStream(destination);
BufferedInputStream bis = new BufferedInputStream(in);
BufferedOutputStream bos = new BufferedOutputStream(out);
byte[] buf = new byte[8192];
int len;
while ((len = bis.read(buf)) > 0) {
bos.write(buf, 0, len);
}
bis.close();
bos.close();
}
public static void copyWithFileChannel(String source, String destination) throws IOException {
FileChannel inChannel = new FileInputStream(source).getChannel();
FileChannel outChannel = new FileOutputStream(destination).getChannel();
ByteBuffer buf = ByteBuffer.allocate(8192);
while (inChannel.read(buf) != -1) {
buf.flip();
outChannel.write(buf);
buf.clear();
}
inChannel.close();
outChannel.close();
}
public static void copyWithUtils(String source, String destination) throws IOException {
FileUtils.copyFile(new File(source), new File(destination));
}
}Key observations from the code:
Using raw FileInputStream / FileOutputStream reads and writes 8 KB buffers in a tight loop – a classic BIO pattern.
Wrapping the streams with BufferedInputStream and BufferedOutputStream adds an 8 KB in‑memory buffer, reducing the number of system calls when the read size is small.
When the read size approaches or exceeds the buffer size, the performance difference between buffered and unbuffered streams diminishes.
The FileChannel version uses NIO buffers ( ByteBuffer) and the channel’s read / write methods, demonstrating a non‑blocking style under the hood.
Apache Commons FileUtils.copyFile implementation
The method internally creates FileInputStream and FileOutputStream, obtains their channels, and then uses FileChannel.transferFrom to move data. The core logic (simplified) is:
private static void doCopyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException {
if (destFile.exists() && destFile.isDirectory()) {
throw new IOException("Destination '" + destFile + "' exists but is a directory");
}
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel input = null;
FileChannel output = null;
try {
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
input = fis.getChannel();
output = fos.getChannel();
long size = input.size();
long pos = 0;
long count;
while (pos < size) {
count = (size - pos) > FILE_COPY_BUFFER_SIZE ? FILE_COPY_BUFFER_SIZE : (size - pos);
pos += output.transferFrom(input, pos, count);
}
} finally {
IOUtils.closeQuietly(output);
IOUtils.closeQuietly(fos);
IOUtils.closeQuietly(input);
IOUtils.closeQuietly(fis);
}
if (srcFile.length() != destFile.length()) {
throw new IOException("Failed to copy full contents from '" + srcFile + "' to '" + destFile + "'");
}
if (preserveFileDate) {
destFile.setLastModified(srcFile.lastModified());
}
}Because the implementation relies on FileChannel.transferFrom, the actual data movement uses NIO, even though the surrounding API appears as a high‑level BIO utility.
Summary of Core Concepts
Byte streams operate on raw byte arrays; character streams add a Unicode conversion layer, which can be costly and may cause encoding issues.
Use character streams for text data and byte streams for binary data such as images or audio.
Buffered streams improve performance for small read/write operations by reducing system‑call overhead.
When copying large files, NIO channels ( FileChannel) and methods like transferFrom provide a more scalable solution.
High‑level utilities (e.g., Apache Commons FileUtils.copyFile) may internally choose BIO or NIO based on the method implementation.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Shepherd Advanced Notes
Dedicated to sharing advanced Java technical insights, daily work snippets, and the power of persistent effort.
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.
