JavaのBufferedInputStreamクラスとmarkSupportedメソッドを解説!マーク機能が使えるか確認する方法
生徒
「Javaでストリームの読み取り位置を戻せるって聞いたんですが、全部のクラスでできるんですか?」
先生
「全部ではありません。markやresetが使えるかは、クラスによって違うんです。その確認に使うのがmarkSupportedメソッドです。」
生徒
「なるほど、使えるかどうかを調べる方法があるんですね。それって実際にはどうやって使うんですか?」
先生
「それでは、BufferedInputStreamとmarkSupportedメソッドの使い方を詳しく見ていきましょう。」
1. BufferedInputStreamクラスとは
BufferedInputStreamは、Javaでデータをバッファに一時的にためながら読み取るためのクラスです。バッファ機能があることで、読み取り操作の効率が向上し、パフォーマンスの高い入出力が可能になります。
ファイルやネットワークなどからデータを1バイトずつ読み込むよりも、BufferedInputStreamを使えばまとめてデータを読み込んでくれるので、処理が速くなります。特に大きなファイルを読み取るときに便利です。
2. markとresetは便利な機能
BufferedInputStreamでは、ストリームの現在位置を記録しておき、後からその位置に戻ることができます。それを実現するのがmarkとresetメソッドです。
たとえば、ファイルの先頭から少し読み取ってみて、必要があれば最初から再読み込みしたいといったケースで役立ちます。ただし、すべてのストリームでmarkやresetが使えるわけではありません。そこで登場するのがmarkSupportedメソッドです。
3. markSupportedメソッドとは?
markSupportedメソッドは、現在使っているInputStreamがmarkとresetに対応しているかどうかを確認するためのメソッドです。戻り値はboolean型で、対応していればtrue、非対応ならfalseが返ります。
たとえば、FileInputStream単体ではmarkに対応していませんが、それをBufferedInputStreamでラップすれば、mark機能が使えるようになります。
4. markSupportedメソッドの使い方を見てみよう
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class MarkSupportedExample {
public static void main(String[] args) {
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("sample.txt"))) {
if (bis.markSupported()) {
System.out.println("このストリームはmarkに対応しています。");
} else {
System.out.println("このストリームはmarkに対応していません。");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
このストリームはmarkに対応しています。
このように、BufferedInputStreamではmarkSupportedメソッドがtrueを返すため、安心してmarkやresetを使うことができます。
5. markSupportedがfalseの場合はどうする?
もし、使っているストリームでmarkSupportedメソッドがfalseを返した場合は、そのままではmarkやresetは使えません。
その場合は、BufferedInputStreamやPushbackInputStreamなど、mark対応のクラスでラップすることで対応できます。たとえば以下のように使います。
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
public class WrapExample {
public static void main(String[] args) throws Exception {
InputStream original = new FileInputStream("sample.txt");
InputStream wrapped = new BufferedInputStream(original);
System.out.println("mark対応: " + wrapped.markSupported());
}
}
mark対応: true
BufferedInputStreamで包むことで、もともと対応していなかったFileInputStreamも、mark機能が使えるようになります。
6. markSupportedとバッファサイズの関係
markSupportedメソッドがtrueを返しても、markやresetが常に成功するとは限りません。内部バッファのサイズが小さいと、読み込みが進むにつれてマーク位置のデータが失われる可能性があります。
そのため、markを使うときは、必要な分の読み取りバイト数(readlimit)をしっかり指定し、必要に応じてバッファサイズを大きくすることも検討しましょう。
7. markSupportedの使いどころと実用例
Javaでストリーム処理を行う際には、入力の途中で条件分岐したり、データを戻して再解析する場面があります。そのような場合にmarkSupportedを使って事前に確認しておけば、安全にmarkとresetが使えます。
特に以下のようなシーンで役立ちます。
- ファイルの先頭を読み取って形式判定後に戻す処理
- データ解析中に複数の解釈候補を試したいとき
- ネットワーク経由で受信したバイナリデータのヘッダー解析
このような実用例でも、markSupportedを先に呼び出すことで、対応可否を事前に把握し、処理の安全性と信頼性を高めることができます。
まとめ
ここまで、Javaにおけるストリーム操作の強力な味方であるBufferedInputStreamと、その柔軟性を支えるmarkSupportedメソッドについて詳しく解説してきました。Javaでファイルやデータを扱う際、単純に「読み込むだけ」なら基本的なInputStreamで事足りますが、実務レベルのアプリケーション開発では「一度読み取ったデータをもう一度見直したい」という場面が頻繁に発生します。
そこで重要になるのが、ストリームの現在位置を記憶するmarkメソッドと、その記憶した位置まで巻き戻すresetメソッドです。しかし、これらはすべての入力ストリームで提供されているわけではありません。もし非対応のストリームに対して不用意にこれらを実行してしまうと、実行時にエラー(IOException)が発生し、システム全体のクラッシュを招く恐れがあります。そうしたリスクを未然に防ぎ、プログラムの堅牢性を確保するために欠かせないのが、今回学んだmarkSupportedメソッドによる事前の可否確認なのです。
Javaストリーム操作の要点:BufferedInputStreamの真価
BufferedInputStreamは単なる高速化ツールではありません。メモリ上に「バッファ」という一時的なデータの溜め場を作ることで、物理的なデバイス(ディスクやネットワーク)へのアクセス回数を劇的に減らしてくれます。この仕組みがあるからこそ、私たちは「データの巻き戻し」という高度な操作を、効率を損なうことなく実現できるのです。
特筆すべきは、ラップ(Wrap)という手法です。元のストリームがどんなに機能不足であっても、BufferedInputStreamで包み込んであげるだけで、あたかも高機能なストリームであるかのように振る舞わせることができます。これはオブジェクト指向設計における「デコレーターパターン」の典型例であり、JavaのI/Oライブラリの美しさでもあります。
実務で役立つ!さらに高度なサンプルプログラム
ここでは、実際の現場でよく遭遇する「データの種類を先頭数バイトで判定し、その後改めて最初から全データを処理する」という流れを想定した、より実践的なコードを紹介します。markSupportedを確認しながら、安全にストリームを操作する実装例を見てみましょう。
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class AdvancedStreamHandling {
public static void main(String[] args) {
// ファイルパスは適宜読み替えてください
String targetFile = "data_source.bin";
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(targetFile))) {
// 1. まずはmark機能が使えるかチェック
if (!bis.markSupported()) {
System.out.println("このストリームはマーク機能をサポートしていません。処理を中断します。");
return;
}
// 2. 現在位置をマーク(readlimitには十分なサイズを指定)
// ここでは先頭1024バイト以内なら戻れるように設定
bis.mark(1024);
System.out.println("ストリームの開始位置をマークしました。");
// 3. 先頭の数バイトを読み取ってデータ形式を判定
byte[] header = new byte[4];
int readByte = bis.read(header);
if (readByte != -1) {
System.out.print("ヘッダー情報: ");
for (byte b : header) {
System.out.printf("%02X ", b);
}
System.out.println();
}
// 4. マークした位置(ファイルの先頭)に巻き戻す
bis.reset();
System.out.println("ストリームをリセットしました。これより最初から全データを読み込みます。");
// 5. 本番のデータ読み込み処理(例として最初の数文字を表示)
int data;
int count = 0;
while ((data = bis.read()) != -1 && count < 10) {
System.out.print((char) data);
count++;
}
System.out.println("... 読み込み完了");
} catch (IOException e) {
System.err.println("入出力エラーが発生しました: " + e.getMessage());
}
}
}
このコードのポイントは、bis.mark(1024)で指定している数値です。これは「どれだけの距離を読み進めても、マークを保持し続けるか」というバッファの有効期限のようなものです。これを超えて読み進めると、reset()が失敗する可能性があるため、余裕を持った設計が必要になります。
エンジニアが知っておくべき周辺知識
markSupportedを理解したら、次に意識したいのは「例外処理の徹底」と「リソースの解放」です。上記のサンプルでも使っているtry-with-resources構文は、Java 7以降の標準的な書き方であり、処理が終わった後に自動でストリームを閉じてくれます。これにより、メモリリークやファイルのロックといった初歩的なミスを防ぐことができます。
また、より柔軟な「読み戻し」が必要な場合は、PushbackInputStreamというクラスも存在します。こちらはマークして戻るのではなく、「読み取ったデータをストリームに押し戻す」という一風変わった動作をします。用途に合わせてこれらのクラスを使い分けられるようになると、Javaエンジニアとしてのレベルが一段階上がること間違いなしです。
生徒
「先生、ありがとうございました!markSupportedメソッドのおかげで、なぜBufferedInputStreamでラップするのが推奨されるのか、その理由がはっきり分かりました。単に速くなるだけじゃなくて、位置を戻すっていう高度な機能も追加されていたんですね。」
先生
「その通りです。プログラムを組むときは『きっと動くだろう』ではなく、『この機能が本当に使えるのか?』を事前に確認する癖をつけるのが大事ですよ。特に外部のファイルや通信を扱うときは、状況が刻一刻と変わりますからね。」
生徒
「確かに。markSupportedがfalseだったらどうしようって不安でしたが、BufferedInputStreamで包めば解決できると知って安心しました。これでファイルヘッダーの解析プログラムも自信を持って書けそうです!」
先生
「いい意気込みですね。あ、ただし一つだけ注意点。会話でも触れたけど、markの引数に指定するバッファサイズ(readlimit)をケチりすぎないようにね。大きなデータを扱うときは、メモリとのバランスを考えるのもプロの仕事ですよ。」
生徒
「はい!メモリのことも意識しながら、安全で効率的なコードを目指します。リセットした後に最初から読み直せるのは、デバッグもしやすそうでワクワクします!」
先生
「そのワクワクが大切です。JavaのI/Oは奥が深いので、次はPrintWriterやBufferedReaderなど、文字ベースのストリームも勉強してみると、さらに視野が広がりますよ。頑張ってくださいね。」