「IO」と「NIO」をざっくり一言でいうと
まず一番上のイメージだけ先に置きます。
従来の java.io は、
「ストリームを通して、データを順番に流す I/O(線でつながったイメージ)」
java.nio は、
「バッファとチャネルを使って、より柔軟・高性能に扱える I/O(メモリの塊をやり取りするイメージ)」
です。
そして、実務的にはもう一つ大事な違いがあります。
ファイルパスやファイル操作
従来:File クラス(java.io.File)
NIO:Path / Paths / Files(java.nio.file)
今から新しく書くコードでは、基本的に「ファイルパスやファイル操作は NIO(Path / Files)、ストリームは IO(InputStream / Reader)か NIO(Channel)」という、両者のいいとこ取りで使うことになります。
古い IO(java.io)の特徴と「できること」
ストリーム指向:InputStream / OutputStream / Reader / Writer
java.io は、「ストリーム(流れ)」を基本に考えます。
InputStream / OutputStream
バイト単位の流れ(画像・PDF・バイナリなど)
Reader / Writer
文字単位の流れ(テキスト)
たとえば、ファイルからテキストを読む典型的なコードはこうでした。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class IoReadExample {
public static void main(String[] args) {
try (BufferedReader reader =
new BufferedReader(new FileReader("data.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Javaこの世界では、「データは InputStream/Reader から、ひたすら一方向に流れてくる」と考えます。
コードの感覚としては、ホースから水が流れてきて、それを順番にすくっているようなイメージです。
IO のメリットと限界
メリットは、直感的で分かりやすいことです。
「データを上から順番に読む」「1 行ずつ処理する」といった処理は、とても素直に書けます。
一方で、古い IO には限界もあります。
メモリマップドファイルのような高性能な仕組みがない
複雑なファイルシステム操作(シンボリックリンク、監視など)が弱い
ネットワークで大量の接続を非同期に扱うのがつらい
これを補うために後から入ってきたのが、java.nio です。
NIO(New IO)の基本コンセプト:バッファとチャネル
バッファ(Buffer)という「メモリの塊」
NIO の世界では、「データの塊」を表す中心が Buffer です。
最もよく使われるのは ByteBuffer です。
import java.nio.ByteBuffer;
public class BufferBasic {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10); // 10バイト分のバッファ
buffer.put((byte) 1);
buffer.put((byte) 2);
buffer.put((byte) 3);
buffer.flip(); // 読み出しモードに切り替え
while (buffer.hasRemaining()) {
byte b = buffer.get();
System.out.println(b);
}
}
}
Javaここでポイントになるのが、NIO のバッファには「位置(position)」「制限(limit)」「容量(capacity)」という状態があることです。
書き込みモード(put)では、「今どこまで書いたか(position)」を進めながらデータを詰めていき、
読み込みモード(get)に切り替えるときに flip() して、「0 から position までを読む」ように方向を変えます。
これは一気に「低レベル」な世界に見えますが、
IO:ストリームを通してデータをなめる
NIO:メモリ上のバッファを自分で動かしながら読む/書く
という発想の違いを理解しておくと、だんだんイメージがつかめてきます。
チャネル(Channel)という「双方向の通路」
InputStream / OutputStream が「片方向の流れ」なのに対して、
NIO では Channel が「データの通り道」を表します。
例として、ファイルを NIO で読むときは FileChannel を使えます。
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.*;
public class NioFileReadExample {
public static void main(String[] args) throws IOException {
Path path = Path.of("data.bin");
try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = channel.read(buffer); // バッファに読み込む
while (bytesRead != -1) {
buffer.flip(); // 読み出しモードに
while (buffer.hasRemaining()) {
byte b = buffer.get();
System.out.print(b + " ");
}
buffer.clear(); // 再び書き込みモードに
bytesRead = channel.read(buffer);
}
}
}
}
Javaここでは、
チャネル(FileChannel)
バッファ(ByteBuffer)
という二つを組み合わせて、「塊としてデータをやり取りしている」ことが分かります。
ストリームと違ってチャンネルはたいてい双方向であり(読みも書きもできる)、
さらに NIO 2 の世界では、非同期 I/O やセレクタと組み合わせることで、
大量の接続を効率よく扱えるようにもなります。
初心者の段階では、
IO:InputStream / OutputStream / Reader / Writer
NIO:Channel + Buffer
という構造を頭に置いておけば十分です。
ファイルとパスの扱い:File vs Path / Paths / Files
IO の File クラスの限界
java.io.File は、すでに説明したとおり、
ディスク上のファイルやディレクトリの「場所」や「属性」を扱う古いクラス
です。
存在確認
ファイルかディレクトリか
作成・削除・リネーム
などの操作はできますが、
パスの正規化(../ を消すなど)が弱い
シンボリックリンク、属性管理が貧弱
パス操作が OS 依存で、使いづらい
といった弱点があります。
NIO の Path / Paths / Files
NIO(正確には NIO.2 と呼ばれる Java 7 以降の拡張)では、
よりモダンなパス・ファイル API として java.nio.file パッケージが導入されました。
Path
ファイルやディレクトリまでの「パス」を表すインターフェース
OS に依存しない
Paths
Path を作るためのヘルパークラス(Java 11 以降は Path.of 推奨)
Files
Path を使って、存在確認・コピー・削除・読み書き・属性取得などを行うユーティリティクラス
例えば、テキストファイルを全行読むなら:
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.util.List;
public class NioFilesRead {
public static void main(String[] args) throws IOException {
Path path = Path.of("data.txt");
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
for (String line : lines) {
System.out.println(line);
}
}
}
Javaまた、ファイルのコピーも一行です。
Path source = Path.of("data.txt");
Path target = Path.of("backup/data.txt");
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
Java実務的には、この「Path / Files」を使うかどうかが、
「今風の NIO 的な書き方ができているか」の分かれ目になります。
同期 I/O と非同期(ノンブロッキング)I/O の違い
古い IO の基本は「ブロッキング I/O」
InputStream.read() や Reader.read() は、「データが来るまで待つ(ブロックする)」メソッドです。
read() を呼んだスレッドは、
相手がデータを送ってくるまで、止まったままになります。
ファイル I/O でも、実行中のスレッドは「読み終わるまで」待たされます。
単純なプログラムなら分かりやすい反面、
- 大量のクライアントからの接続をさばきたいサーバ
- 長時間かかる I/O を待ちながら、他の処理も進めたい GUI アプリ
などでは、スレッド数が増えすぎたり、レスポンスが悪くなったりと、問題が出やすくなります。
NIO のノンブロッキング I/O(主にネットワーク向け)
NIO は、「ノンブロッキング I/O」や「セレクタ」をサポートします。
ざっくりいうと、
ソケットをノンブロッキングモードにしておく
データが読み書きできる状態かどうかを、Selector で監視する
I/O が準備できたソケットに対してだけ処理を行う
というスタイルです。
これにより、
少ないスレッドで大量の接続を捌くことが可能になります。
ただし、これは初心者向けとしてはかなり難易度が高いので、
IO は「基本ブロッキング」
NIO は「必要ならノンブロッキングで書ける(特にネットワーク)」
というレベルで押さえておけば十分です。
ファイル I/O に関しては、NIO も実質的にはブロッキングであることが多いです。
初心者として「何を IO で書き」「何を NIO で書くか」
ファイルを読む/書くならどうする?
今から新しくコードを書くなら、次のように考えるとバランスが良いです。
パス・ファイル操作(存在チェック、コピー、削除など)
→ NIO の Path / Files
テキストファイルをシンプルに読む/書く
→ Files.readAllLines / Files.readString / Files.writeString
または Files.lines + Stream
細かい行単位処理や、古い本に載っているコードを読む
→ BufferedReader / BufferedWriter(IO)
バイナリファイルを高速に扱う、特殊な用途
→ 必要に応じて FileChannel + ByteBuffer(NIO)
つまり、IO と NIO はどちらか一方だけを使うのではなく、
パスとファイル操作は NIO
ストリーム的なテキスト処理は IO(+NIO の Files ユーティリティ)
という混在が「普通」になっています。
ネットワークならどうする?
小規模でシンプルなクライアント/サーバー
→ Socket / ServerSocket(IO)で十分
大量接続をさばく高性能サーバー
→ NIO の SocketChannel / Selector などを検討
最初から NIO のノンブロッキングネットワークをやる必要はまったくありません。
IO で基本を押さえ、そのあと必要に応じて NIO に踏み込めば十分です。
まとめ:NIO と IO の違いを自分の中でこう整理する
初心者向けに、「IO と NIO の違い」を一言で整理すると、こうなります。
IO(java.io)
ストリーム指向(InputStream / Reader)で、順番にデータを流して扱う。
シンプルで分かりやすいが、古くて機能的に限界もある。
NIO(java.nio / java.nio.file など)
バッファ+チャネル指向(Buffer / Channel)で、より高性能で柔軟に扱える。
Path / Files により、ファイルやパス操作もモダンに。
必要に応じてノンブロッキング(特にネットワーク)も書ける。
実務的なおすすめは、
- ファイルパスとファイル操作は NIO(Path / Files)
- テキスト I/O は IO(BufferedReader / BufferedWriter)+ NIO の便利メソッド(Files.readAllLines など)
- バイナリの高度な処理や大量接続のネットワークは NIO(Channel / Selector)
という使い分けです。

