はじめに
Javaでストリームからデータを読み込む際、「一度読み込んだ内容を処理前に戻したい」という場面があります。たとえばファイル形式を先頭数バイトで判定したり、構文解析中にトークンを先読みして分岐処理を行ったりするケースです。
このような先読み処理に便利なのが、PushbackInputStream
と PushbackReader
です。これらは読み込んだデータを「押し戻す(push back)」ことで、再び読み直すことを可能にします。
この記事では、それぞれの使い方や違い、実践的な活用方法、そして利用時に注意すべきポイントまでをわかりやすく解説します。構文解析や独自フォーマットの読み込み処理など、実務に役立つ知識としてぜひ参考にしてください。
PushbackInputStreamとは?
PushbackInputStream
は、InputStream
を拡張するクラスで、読み込んだバイトデータを一度ストリームに「戻す」ことができる特殊な入力ストリームです。
たとえば、バイナリファイルの先頭数バイトを読み取ってフォーマットを判別し、その結果によって処理を切り替えるといった場面で活躍します。不要だったバイトは unread()
メソッドで戻せるため、その後の処理で再び通常どおり読み直すことができます。
このように、PushbackInputStream
はバイナリデータの「先読み」と「戻し」を組み合わせた柔軟な処理を可能にし、独自フォーマットやプロトコルの解析などに適したツールとなっています。
基本的な使い方
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
public class PushbackInputStreamExample {
public static void main(String[] args) {
try (InputStream in = new FileInputStream("data.bin");
PushbackInputStream pbIn = new PushbackInputStream(in)) {
// 最初の1バイトを読む
int firstByte = pbIn.read();
if (firstByte == 0xFF) {
// 特定の値の場合、特殊処理を行う
System.out.println("特殊フォーマットとして処理します");
} else {
// 特定の値以外の場合、読み戻す
pbIn.unread(firstByte);
}
// この時点で、戻したバイトも含めて通常通り読める
processFile(pbIn);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void processFile(InputStream in) throws IOException {
// ファイルの残りを処理
int data;
while ((data = in.read()) != -1) {
// 処理ロジック
}
}
}
PushbackReaderとは?
PushbackReader
は、Reader
を拡張したクラスで、読み込んだ文字をストリームに戻すことができる機能を持っています。テキストデータを扱う際に、「先に1文字読んでみてから処理を決めたい」といった場面に便利です。
たとえば、テンプレートエンジンや構文解析処理では、次に現れる文字が特定の記号(例:<
や {
)かどうかを判断して処理を分岐させることがあります。このとき、判断材料として文字を一度読み込んだ後、その文字を unread()
で戻せば、通常の読み取り処理に影響を与えずに柔軟な制御が可能になります。
PushbackReader
は、文字単位で扱う構造や構成ファイル、簡易的なパーサなど、テキストベースの先読み処理に最適なクラスです。
基本的な使い方
import java.io.FileReader;
import java.io.IOException;
import java.io.PushbackReader;
import java.io.Reader;
public class PushbackReaderExample {
public static void main(String[] args) {
try (Reader reader = new FileReader("index.html");
PushbackReader pbReader = new PushbackReader(reader)) {
// 最初の1文字を読む
int ch = pbReader.read();
if (ch == '<') {
// HTMLタグの開始かもしれない
pbReader.unread(ch);
parseHTMLTag(pbReader);
} else {
// 通常のテキストとして処理
processText(pbReader, ch);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void parseHTMLTag(PushbackReader reader) throws IOException {
// HTMLタグの解析処理
System.out.println("HTMLタグを解析中...");
}
private static void processText(PushbackReader reader, int firstChar) throws IOException {
// 通常テキストの処理
System.out.println("テキストを処理中...");
}
}
PushbackInputStreamとPushbackReaderの違い
PushbackInputStream
と PushbackReader
はどちらも「読み込んだデータを戻す」ためのクラスですが、扱うデータの種類や使用目的が異なります。以下の比較表をご覧ください。
項目 | PushbackInputStream | PushbackReader |
---|---|---|
対象データ | バイナリ(byte) | テキスト(char) |
基底クラス | InputStream | Reader |
主な用途 | 画像・音声・独自バイナリ形式 | 設定ファイル・テンプレート・構文解析 |
戻せる単位 | バイト(byte) | 文字(char) |
バイナリ形式のデータ(画像、音声、独自フォーマットなど)を扱う場合は PushbackInputStream
、テキストファイルやテンプレートのような文字列ベースのデータには PushbackReader
を使うのが基本です。
用途に応じて正しく使い分けることで、先読み処理の柔軟性と可読性が向上します。
使用上の注意点とベストプラクティス
Pushback系のストリームは便利な反面、使い方を誤ると思わぬバグや制限に直面することがあります。ここでは、安全かつ効果的に使うための注意点とベストプラクティスを紹介します。
複数のデータを戻す場合はバッファサイズの指定が必須
PushbackInputStream
や PushbackReader
は、内部に戻したデータを一時保持するバッファを持っています。デフォルトのバッファサイズは「1」で、1バイトまたは1文字しか戻せません。
複数バイト/文字を unread()
したい場合は、コンストラクタで十分なバッファサイズを指定してください。
// 悪い例:バッファ不足でIOExceptionが発生
PushbackInputStream pbIn = new PushbackInputStream(in); // バッファサイズ1
byte[] data = new byte[4];
pbIn.read(data);
pbIn.unread(data); // IOException!
// 良い例:十分なバッファサイズを指定
PushbackInputStream pbIn = new PushbackInputStream(in, 4); // バッファサイズ4
byte[] data = new byte[4];
pbIn.read(data);
pbIn.unread(data); // OK
mark()
/ reset()
は使用不可
多くの InputStream
や Reader
では mark()
/ reset()
による位置復元が可能ですが、Pushback系クラスでは非対応です(markSupported()
は常に false
を返します)。
代わりに unread()
を使って読み戻し処理を行う必要があります。
スレッドセーフではない
PushbackInputStream
と PushbackReader
はスレッドセーフではありません。複数のスレッドから同時にアクセスする場合は、synchronized
ブロックやラップクラスでの排他制御を検討しましょう。
まとめ:Pushback系ストリームはこう使う
PushbackInputStream
や PushbackReader
は、「読み込みながら判定し、必要に応じてデータを戻す」といった柔軟な入力制御を実現するための便利なクラスです。特に構文解析やファイル形式の自動判別など、先読みが求められるシナリオで重宝します。
PushbackInputStream
はバイナリデータの先読み・判定処理に最適
→ 画像・動画・独自フォーマットの判別などに活用PushbackReader
はテキスト解析やトークン処理に強み
→ 構成ファイルやテンプレートエンジン、シンプルなパーサなどに有効mark()
/reset()
は使えない
→ 明示的にunread()
で戻す設計を意識する- 戻す量に応じたバッファサイズを指定する
→ 複数バイト・文字を戻すときはバッファ不足に注意 - スレッドセーフではない点に注意する
→ 複数スレッドからアクセスする場合は同期処理を追加
Pushback系ストリームは一見ニッチな存在ですが、特定の処理では他に代えがたい役割を果たします。用途と制約を理解して、適切な場面で賢く使いこなしましょう。