プログラミングをしていると必ず出会う InputStream, OutputStream, そして Reader, Writer。 「結局どれを使えばいいの?」「なんでこんなに面倒なループを書くの?」と疑問に思ったことはありませんか? その答えは、**「メモリを節約しながら、あらゆる場所へデータを流すため」**の知恵にあります。
データのやり取りをするとき、一番シンプルなのは「データを全部メモリに乗せる」ことです。しかし、1GBの動画ファイルやAPKファイルをそのままメモリに乗せたら、アプリは即座にクラッシュします。
そこで登場するのが**ストリーム(流体)**という考え方です。
データを受け側と送り側の間に「土管(パイプ)」を通し、データを少しずつ流し込むのです。
InputStream (蛇口): データを読み出す(吸い上げる)側。
OutputStream (排水口): データを書き込む(押し出す)側。
Androidのパッケージインストールなどでよく見る、あの泥臭い while ループの正体は何でしょうか。
Java
byte[] buffer = new byte[16384]; // 16KBのバケツ
int n;
while ((n = is.read(buffer)) >= 0) {
os.write(buffer, 0, n);
}
これは、**「巨大なプール(データ)の水を、小さなバケツ(メモリ)で汲んで、別の場所に運んでいる」**作業です。
どんなにデータが大きくても、**メモリ消費はバケツ1杯分(16KB)**だけで済みます。
このバケツリレーのおかげで、低スペックなスマホでも数GBのゲームをインストールできるのです。
ここで一つ混乱しがちなのが、Reader / Writer の存在です。これらは「文字」を扱うための道具です。
Stream (Byte Stream): 画像、動画、APK、実行ファイルなど、**「バイナリ(生のデータ)」**をそのまま運ぶ。
Reader / Writer (Character Stream): テキストファイルを扱う。背後で**「エンコーディング(UTF-8など)」**を意識し、バイト列を文字として解釈してくれる。
「画像を送りたいなら Stream」「日記を書きたいなら Writer」と使い分けるのが正解です。
OutputStream は抽象クラス(契約)です。システムが提供してくれる OutputStream もあれば、ファイルに書き込むもの、ネットワークに送るものもあります。
特筆すべきは、**「書き込み側(あなたのアプリ)は、相手の正体を知らなくていい」**ということです。
「これが土管の入り口です。ここに
writeしてください」
と言われれば、相手が物理ファイルだろうが、Androidのシステムプロセスだろうが、はるか彼方のサーバーだろうが、あなたのコードは全く同じ write ループで動きます。これこそがオブジェクト指向の「ポリモーフィズム」の威力です。
ストリームの真骨頂は「連結」にあります。
普通の OutputStream に、
GZIPOutputStream(圧縮)を繋ぎ、
さらに CipherOutputStream(暗号化)を繋ぐ。
こうすると、あなたはただデータを流し込むだけで、**「暗号化されて圧縮されたデータ」**が自動的に送り出されます。
一見無駄に見える「バケツリレー」のコードは、実は**「どんなに大きなデータでも、どんなに貧弱な環境でも、どんな送り先に対しても、安全かつ確実にデータを届ける」**ための、コンピュータ界の偉大な発明なのです。
次に OutputStream を見かけたときは、「あ、今はシステムの土管にバケツで水を流し込んでるんだな」と思い出してみてください。