Javaのストリームを完全に理解したので分かりやすく説明する。

プログラミング

「ためになった!」「よかった!」という人は是非シェアしていただけると嬉しいです!

この記事は

読者1: Javaのストリームって何!?
読者2: Javaやったけど、ストリームいっぱいありすぎて分からん!
読者3: ストリーム使ったり使わなかったりするのなんで!

という人向けにストリームを根本から理解しようということをテーマにした記事です。

ストリームとは

端的に説明すると情報を作る、消費することの抽象的な概念のことです。
もっと簡単にいうとデータとプログラム間の0と1の羅列の流れのことです。
もっと正確に知りたいかたはoracle公式ドキュメントを読むことをおすすめします。

ストリームとは

JavaのI/Oストリームの構造

ふわっとしていますが、ストリームについて説明をしました。
次にJavaでのI/Oストリームについて説明していきます。

Javaには大きく2つのストリームがあります。

  1. バイトストリーム
  2. キャラクターストリーム(文字ストリーム)

(しかし低いレベルで見るとキャラクターストリームは実際バイトストリーム由来です。あくまでも文字を効率的に使うために存在しています。)

この2つのストリームにそれぞれクラスの階層構造が存在いるので以下で説明していきます。
それぞれのクラスの階層構造はパラレルになっています。その点を意識して読んでみると理解しやすいと思います。

バイトストリーム

バイトストリームはバイトデータを扱うI/Oストリームです。
バイトストリームのクラス階層構造の一番上にはInputStreamとOutputStreamの2つの抽象クラスが存在しています。

そして、この2つの抽象クラスを継承するサブクラスが多数存在します。
主なバイトストリームクラスを表にしました。Javaドキュメントより引用

Byte Stream Class説明
BufferedInputStreamBufferedInputStreamは、ほかの入力ストリームに機能、特に入力をバッファに格納する機能とmarkおよびresetメソッドをサポートする機能を追加します。
BufferedOutputStreamバッファリングされた出力ストリームを実装します。
ByteArrayInputStreamByteArrayInputStreamは、ストリームから読み込むことができるバイトを格納する内部バッファを保持しています。
ByteArrayOutputStreamデータがバイト配列に書き込まれる出力ストリームを実装します。
DataInputStreamデータ入力ストリームにより、アプリケーションは、プリミティブ型のJavaデータをベースとなる入力ストリームからマシンに依存せずに読み込むことができます。
DataOutputStreamデータ出力ストリームを使うと、アプリケーションはプリミティブ型のJavaデータを移植性のある形で出力ストリームに書き込むことができます。
FileInputStreamFileInputStreamは、ファイル・システム内のファイルから入力バイトを取得します。
FileOutputStreamファイル出力ストリームは、FileまたはFileDescriptorにデータを書き込むための出力ストリームです。
FilterInputStreamFilterInputStreamは、データの基本的なソースとして使用するためのその他の入力ストリームを格納します。データを途中で変換することや、追加機能を提供することもあります。
FilterOutputStreamこのクラスは、出力ストリームをフィルタ処理するすべてのクラスのスーパー・クラスです。
InputStreamこの抽象クラスは、バイト入力ストリームを表現するすべてのクラスのスーパー・クラスです。
LineNumberInputStream
非推奨。このクラスは、文字がバイトによって適切に表現されるという誤った認識を前提としています。
ObjectInputStream事前にObjectOutputStreamを使って作成されたプリミティブ・データとプリミティブ・オブジェクトを直列化復元します。
ObjectOutputStreamObjectOutputStreamは、プリミティブ・データ型とJavaオブジェクトのグラフをOutputStreamに書き込みます。
OutputStreamこの抽象クラスは、バイト出力ストリームを表現するすべてのクラスのスーパー・クラスです。
PipedInputStreamパイプで連結された入力ストリームは、パイプで連結された出力ストリームに接続するようにします。これによって、パイプで連結された入力ストリームが提供するデータ・バイトは、パイプで連結された出力ストリームにすべて書き込まれます。
PipedOutputStreamパイプで連結された出力ストリームをパイプで連結された入力ストリームに接続することで、通信パイプを作成することができます。
PrintStreamPrintStreamは、ほかの出力ストリームに機能、具体的には、さまざまなデータ値の表現を簡易的に出力する機能を追加します。
PushbackInnputStreamPushbackInputStreamは、内部バッファにプッシュバックされたバイトを格納することによって、別の入力ストリーム、つまり”プッシュバック”または”unread”バイトへの機能性を追加します。
SequenceInputStreamSequenceInputStreamは、ほかの入力ストリームを論理的に連結したものを表します。
StringBufferInputStream非推奨。このクラスでは、文字からバイトへの変換が正しく行われません。

共通する点はクラス名の末尾が”Stream”で終わるということです。

キャラクターストリーム(文字ストリーム)

キャラクターストリームは文字を扱うI/Oストリームです。

キャラクターストリームの利点はローカル文字セットに自動で変換してくれることなどがあげられます。またバッファーを利用できるのでOSのオーバーヘッドが減るので効率よく扱えます

キャラクターストリームのクラス階層構造の一番上にはReaderとWriterという2つの抽象クラスが存在します。そしてバイトストリーム同様にこの2つのクラスのサブクラスが存在します。

主なキャラクターストリームのクラスを以下の表にまとめました。 Javaドキュメントより引用

Character Stream Class説明
BufferedReader文字、配列、行をバッファリングすることによって、文字型入力ストリームからテキストを効率良く読み込みます。
BufferedWriter文字をバッファリングすることによって、文字、配列、または文字列を効率良く文字型出力ストリームに書き込みます。
CharArrayReader文字入力ストリームとして使用する文字バッファを実装します。
CharArrayWriterWriterとして使用する文字バッファを実装します。
FileReaderデフォルトのバッファ・サイズを使用して、文字ファイルからテキストを読み取ります。
FilterWriterフィルタ処理された文字ストリームのための抽象クラスです。
LineNumberReader行番号を追跡して管理する、バッファリングされた文字入力ストリームです。
OutputStreamWriterOutputStreamWriterは、文字ストリームからバイト・ストリームへの橋渡しの役目を持ちます。それに書き込まれた文字は、指定されたcharsetを使用してバイトにエンコードされます。
PipedReaderパイプによる文字入力ストリームです。
PipedWriterパイプによる文字出力ストリームです。
PrintWriterオブジェクトの書式付き表現をテキスト出力ストリームに出力します。
PushbackReader文字をストリームにプッシュ・バックできる文字ストリーム・リーダーです。
Reader文字ストリームを読み込むための抽象クラスです。
StringReaderソースが文字列の文字ストリームです。
StringWriter出力を文字列バッファに集める文字ストリームです。この文字列バッファを使って文字列を構築します。
Writer文字ストリームに書き込むための抽象クラスです。

共通する点はクラス名の末尾がReader,Writerで終わるという点です。

3. それぞれのストリームの選び方

前章で2種類のストリーム体系の違いについて書きました。
 すると以下の疑問が生まれると思います。


「ストリームの構造、種類、利点は分かったけど結局どっちを使えばいいの?」

なので、この疑問にお答えします。

選び方は簡単で

  • 文字以外を扱うか、文字を扱うか

です。

  • 文字のみを扱う場合 → キャラクターストリーム
  • 文字以外を扱う場合 → バイトストリーム

となります。

文字のみを扱う場合キャラクターストリームを使うべき理由

理由

  1. バッファーを使うことが出来る
  2. 文字セットに対応している

1. バッファーを使うことが出来る

文字ストリームにはBufferedReaderとBufferedWriterが存在しています。
これらのバッファーを利用したreadLine関数や拡張されたwrite関数が定義されています。
この2つの関数はchar型、char型配列ではなくString型を使用することが出来るので初心者でも非常に扱いやすく
可読性も上がります。
またバッファを扱う利点として、OSのオーバーヘッドが減り効率良くなるという点もあります。

2. 文字セットに対応している

javaでは文字値の保存にUnicode規則を使用しています。そして文字ストリームI/Oによって、
内部のUnicode形式とローカル文字セットとの変換が自動的に行われます。
これによってプログラマの負担を減らすことができます。

文字以外を扱う場合はバイトストリームを使う理由

これの理由は単純でキャラクターストリームで文字以外のデータを扱うことが出来ないからです。

4. おまけ

ストリームと切っても切り離せないものとして3種類の”ストリーム型の変数”が存在します。

  • in
  • out
  • err

の3つです。これはjava.langパッケージのSystemクラスにpublicでstaticでfinalな変数として定義されています。

これは標準入力、標準出力で使う変数です。

System.inはInputStreamクラス(正確に言えばInputStreamクラスを継承した匿名クラス)のインスタンス
System.outとSystem.errはPrintStreamクラスのインスタンスを格納しています。

これらはクラス名からわかる通りバイトストリームを採用しています。
しかしコンソールには文字を入力するのでバイトと文字をいったりきたりするので面倒くさいのですが、

  • OriginalのJavaにはバイトストリームしか存在しなかった(キャラクターストリームはJDK1.1で追加されています)
  • シンプルなプログラムやraw キーボード Inputなどではバイトストリームのほうが好ましいから

という理由から現在でもバイトストリームが使われています。

もちろんキャラクターストリームでラップすることも可能です。

5. まとめ

Javaにはバイトストリームとキャラクターストリームの2種類が存在する。
 それぞれにクラスの階層構造が存在する。(低いレベルで見ればキャラクターストリームはバイトストリームではあるが)
基本的に文字を扱うときはキャラクターストリーム、文字以外を扱うときはバイトストリームを使う。
今回はこれで終わりです。

誤字脱字 分かりにくいところ 間違いなどがありましたらコメント欄にコメントをお願いします。

また、ためになった!よかった!という人は是非記事をシェアしていただけると幸いです

ではよいプログラミングライフを。

コメント

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