Java | Java 標準ライブラリ:NIO と IO の違い

Java Java
スポンサーリンク

「IO」と「NIO」をざっくり一言でいうと

まず一番上のイメージだけ先に置きます。

従来の java.io は、

ストリームを通して、データを順番に流す I/O(線でつながったイメージ)

java.nio は、

バッファとチャネルを使って、より柔軟・高性能に扱える I/O(メモリの塊をやり取りするイメージ)

です。

そして、実務的にはもう一つ大事な違いがあります。

ファイルパスやファイル操作
従来:File クラス(java.io.File
NIO:Path / Paths / Filesjava.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)

バイナリファイルを高速に扱う、特殊な用途
→ 必要に応じて FileChannelByteBuffer(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)

という使い分けです。

タイトルとURLをコピーしました