検索

キーワード


【Java】バイナリファイルの読み込みと出力

  • 公開日:2020-11-30 23:57:34
  • 最終更新日:2020-12-26 23:21:10
【Java】バイナリファイルの読み込みと出力

当記事では、Javaにおけるバイナリファイルの読み書きの方法について説明します。Javaにおけるテキストファイルの読み書きの方法については、以下の記事を参照してください。

【Java】テキストファイルの入出力

Javaの初学者にとっては似たようなクラス名が多く、混乱しがちなトピックですが、それぞれのクラスがどういう働きをするのかを知れば怖くありません。よく起こりがちなエラーへの対処法も含めて、Java Gold SE 11の資格を持つ著者が解説したいと思います。

画像や音声など、文字で表現できないファイルをバイナリファイルといい、Javaにおけるバイナリファイルの読み書きにはバイトストリームという仕組みを扱います。バイトストリームについては、以下の記事を参照してください。

【Java】入出力ストリーム(java.ioパッケージ)

Javaにおけるバイナリファイルの読み取りには、java.ioパッケージのInputStreamを継承したクラスを用い、書き込みにはOutputStreamを継承したクラスを使います。様々なクラスが用意されていますが、当記事では以下のよく使われる3つのクラスについて解説します。

・FileInputStream / FileOutputStream

・BufferedInputStream / BufferedOutputStream

・DataInputStream / DataOutputStream

また、Java上のオブジェクトをシリアライズ(直列化)して外部に出力したり、デシリアライズ(直列化からの復元)して読み取ったりするためのObjectInputStream / ObjectInputStreamというクラスもあります。こちらについては以下の記事で扱っています。

【Java】 シリアライズの紹介!


バイトストリームのメソッド

Javaにおいてバイトストリームを扱う抽象クラスであるInputStream、OutputStreamには、それぞれいくつかのメソッドが用意されています。


InputStreamのメソッド

InputStreamクラスの主要なメソッドは以下の通りです。InputStreamを継承したクラスで呼び出すことが出来ます。

read()メソッドはバイトを読み込むので返り値はbyte型でいいように思うかもしれませんが、read()メソッドは1バイトを0~255までの数字で読み取り、ファイルの終わりに達したときに-1を返すので、返り値はint型になっています。

メソッド名返り値の型説明
read()int
出力ストリームに引数に渡した文字列sを書き込みます。
read(byte[] b)int出力ストリームに引数に渡した文字列sを書き込みます。最後に行の区切り文字を出力して、改行を行います。
read(byte[] b, int off, int len)int入力ストリームから引数に渡したバイト配列bにデータをコピーしますが、読み取り開始インデックスoffを指定し、そこから読み込むバイトの数lenだけ読み取ります。
返り値は読み取られたバイトの数です。ファイルの終わりに達したときは-1を返します。
close()voidストリームをクローズして、メモリへの割り当てを解除します。


OutputStreamのメソッド

OutputStreamクラスのメソッドは以下の通りです。OutputStreamを継承したクラスで呼び出すことが出来ます。

メソッド名返り値の型説明
write(int b)void出力ストリームに1バイトずつ書き込みます。0~255以外の数値を渡した場合は下位8ビットのみを書き込みます。
write(byte[] b)void出力ストリームに引数に渡したバイト配列bを書き込みます。
write(byte[] b, int off, int len)void出力ストリームに引数に渡したバイト配列bを書き込みますが、書き込みを開始するインデックスoffを指定し、そこから書き込むバイトの数lenだけ書き込みます。
flush()voidバッファに格納されている全ての出力バイトストリームを強制的に書き込みます。
close()voidバッファに格納されている全ての出力バイトストリームを強制的に書き込みます。


バイナリファイルの入出力を行う際の注意点

処理が終わった後に必ずclose()メソッドを実行する

Javaにおいて入出力ストリームのインスタンスを扱う際は、処理が終わった後に必ずclose()メソッドを実行して下さい。これを忘れると、使わないストリームにメモリが割り当てらたままになるメモリリークと呼ばれる状態になり、処理が遅くなるなどの不具合の原因となることがあります。

当記事では、サンプルコードにtry-with-resources構文を用いて処理が終わった後に自動的にclose()メソッドを実行するようにしています。そのため、try-with-resources構文について理解に不安がある方は以下の記事を参照してください。

【Java】try - with - resources 構文


例外:FileNotFoundExceptionについて

よくあるエラーとして、「FileNotFoundException」という例外が起こることがあります。これはファイルのパスの場所を間違えて指定した場合などに起きます。パスは絶対パスと相対パスの2種類の表記法がありますが、慣れないうちは絶対パスで指定すると間違いが少なくなります。


絶対パスの簡単な取得方法

ファイルの絶対パスを正確かつ簡単に取得したいときは、指定したいファイルのあるフォルダで、上部のバーを右クリックして「アドレスをテキストとしてコピー」という項目を選択して絶対パスをコピーします。

ファイルの絶対パスのテキストをコピーする方法

例えば、上記の画面で操作を行った場合、「C:\JavaWorkspace\Works」というディレクトリの絶対パスがコピーされますが、「\」はエスケープ文字を表す文字なので、Javaに「\」を認識させたい場合は「\」を「\\」と入力する必要があります。例えば、Worksディレクトリ内の「sample.jpg」の絶対パスを指定したい場合は、C:\\JavaWorkspace\\Works\\sample.jpg」とする必要があります。Eclipse上では、文字列を表す「""」のダブルクォーテーションの間にコピーすれば自動的に「\」が「\\」に変換されます。


Cドライブの残り容量にも注意

また、サンプルコードを実行したとき、「error='ページング ファイルが小さすぎるため、この操作を完了できません。' (DOS error/errno=1455)」というエラーメッセージが表示されることがあります。これは、Cドライブの空き容量が足りないために起こるので、十分な空き容量を確保してからコードを実行するようにしてください。


FileInputStream / FileOutputStream

FileInputStream

FileInputStreamは、ファイルからバイト入力ストリームを作成するためのクラスです。

コンストラクタの引数にはパスを表す文字列、またはFileオブジェクトを取ります。

//コンストラクタに読み取るファイルのパスを表す文字列を渡します。絶対パスでも相対パスでも大丈夫です。
FileInputStream fis = new FileInputStream("ファイルのパス");

//コンストラクタにFileオブジェクトを渡すことも出来ます。
File file = new File("ファイルのパス");
FileInputStream fis = new FileInputStream(file);


FileOutputStream

FileOutputStreamは、ファイルに書き込みを行うためのバイト出力ストリームを作成するクラスです。FileInputStream同様、コンストラクタの引数にはファイルのパス名またはFileオブジェクトを取りますが、第二引数に「true」を指定することで追記を行うことも出来ます。

//コンストラクタに読み取るファイルのパスを表す文字列を渡します。絶対パスでも相対パスでも大丈夫です。
FileOutputStream fos = new FileOutputStream("ファイルのパス");
		
//FileOutputStreamはデフォルトでは指定したファイルの内容を上書きしますが、
//コンストラクタに第二引数としてtrueを渡すと追記するように指定が出来ます。falseを渡すと上書きします。
FileOutputStream fos = new FileOutputStream("ファイルのパス", true);

//コンストラクタにFileオブジェクトを渡すことも出来ます。
File file = new File("ファイルのパス");
FileOutputStream fos = new FileOutputStream(file);
		
//こちらも第二引数としてtrueを渡すと追記するように指定が出来ます。
FileOutputStream fos = new FileOutputStream(file, true);


FileInputStream / FileOutputStreamの実行例

文字を用いないバイナリファイルの例として、画像が挙げられます。Javaでの画像の読み込みはjavax.imageioパッケージを用いるのが一般的ですが、当記事では以下の画像の読み書きを例に、FileInputStream / FileOutputStreamを使った、Javaにおけるバイナリファイルの読み書きについて解説したいと思います。

sample.jpg イメージ

上記の画像を「sample.jpg」という名前にして保存します。


FileInputStream / FileOutputStreamのサンプルコード

import java.io.FileInputStream;
import java.io.FileOutputStream;

public class FileIOStreamSample {
	public static void main(String[] args) {
		
		//try-with-resouses構文を使って、処理が終わった後に自動的にclose()メソッドを呼び出すようにします。
		//「;」を使うことで、リソースを複数指定できます。
		//読み込むファイルにsample.jpg、書き出すファイル名にsample2.jpgを指定しています。
		try (FileInputStream fis = new FileInputStream(".\\sample2.jpg");
				FileOutputStream fos = new FileOutputStream(".\\sample3.jpg")) {
			
			//read()メソッドは読み取ったバイトの情報をint型で返し、ファイルの終わりに達した場合は-1を返します。
			//そのため、fis.read()が-1を返すまでwhileループさせるとファイル全体を読み込むことができます。
			int data;
			while ((data = fis.read()) != -1) {
				
				//読み取った数字をコンソールに出力してみます。
				System.out.println(data);
				
				//読み取ったデータをsample2.jpgに書き込みます。
				fos.write(data);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}


FileInputStream / FileOutputStreamのサンプルコードの実行結果

FileOutputStreamのコンストラクタに渡したパスに新たに「sample2.jpg」というファイルが生成されます。実際に「sample2.jpg」の内容を確認すると、「sample.jpg」と同じ画像になっていることが確認出来ます。

また、コンソールには以下のように出力されます。

246
247
248
249
250
.
.
.

read()メソッドは読み取ったバイトの情報をint型で返すので、0~255までの数字が出力されます。


BuffererInputStream / BufferedOutputStream

BufferedInputStream

BufferedInputStreamは、入力バイトストリームにバッファリング(バッファを利用した読み書きの効率化)の機能を追加するためのクラスです。バッファについては以下の記事で説明しています。

【Java】入出力ストリーム(java.ioパッケージ)

コンストラクタの引数には、InputStreamクラスを継承したクラス(FileInputStreamクラスなど)を取ります。

//コンストラクタにInputStreamクラスのサブクラスを渡します。
FileInputStream fis = new FileInputStream("ファイルのパス");
BufferedInputStream bis  new BufferedInputStream(fis);


BufferedOutputStream

BufferedOutputStreamは、出力バイトストリームにバッファリングの機能を追加するためのクラスです。

コンストラクタの引数には、OutputStreamクラスを継承したクラス(FileInputStreamクラスなど)を取ります。

//コンストラクタにOutputStreamクラスのサブクラスを渡します。
FileOutputStream fos = new FileOutputStream("ファイルのパス");
BufferedOutputStream bos  new BufferedOutputStream(fos);


BufferedInputStream / BufferedOutputStreamの実行例

FileInputStream/FileOutputStreamのときと同様の画像ファイル「sample.jpg」を使って、BufferedInputStream / BufferedOutputStreamを用いてバッファを利用したバイナリファイルの読み書きについて解説したいと思います。

バイナリファイル読み書き用の画像ファイル


BufferedInputStream / BufferedOutputStreamのサンプルコード

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Arrays;

public class BufferedIOSample {
	public static void main(String[] args) {
		//読み込むファイルにsample.jpg、書き出すファイル名にsample3.jpgを指定しています。
		try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(".\\sample.jpg"));
				BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(".\\sample3.jpg"))) {

			//読み取ったデータを格納するためのバッファとなるバイト配列を宣言します。
			//配列の長さは、1024の倍数にするのが一般的です。
			byte[] data = new byte[1024];

			//read(byte[] b)メソッドで返ってくる、読み取ったバイト配列の長さを格納するためのint型変数を宣言します。
			int len;

			//read(byte[] b)メソッドはバイト配列に読み取ったバイトの情報をコピーしますが、
			//読み取ったバイト配列の長さをint型で返し、ファイルの終わりに達した場合は-1を返します。
			//そのため、bis.read(data)が-1を返すまでwhileループさせるとファイル全体を読み込むことができます。
			while ((len = bis.read(data)) != -1) {

				//dataをコンソールに出力してみます。
				//配列をコンソール出力するには、Arrays.toString(Object[] obj)を使います。
				System.out.println(Arrays.toString(data));

				//read(byte[] b, int off, int len)メソッドで、readで読み取った長さの分だけ、
				//バイト配列dataの内容をsample3.jpgに書き込みます。
				bos.write(data, 0, len);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}



BufferedInputStream / BufferedOutputStreamのサンプルコードの実行結果

FileOutputStreamのコンストラクタに渡したパスに新たに「sample3.jpg」が出力されたことが確認できます。実際に「sample3.jpg」の内容を確認すると、「sample.jpg」と同じ画像になっていることが確認出来ます。

また、コンソールに以下のように出力されます。

[-1, -40, -1, -32, 0, ...]
[103, -96, 88, 107, -87, ...]
[122, -41, -8, 85, -15, ...]
[35, -73, -5, 101, -52, ...]
[-14, -33, 110, -42, 60, ...]
[100, 116, -29, 107, -70, ...]

dataはbyte配列であるため、出力される数値はbyte型の範囲である-128~127までの数値になっています。FileInputStreamの出力結果と違ってマイナスの数値が出てくるのは、byte型の最上位ビットである8ビット目が符号を表すため、int型で128~255までの数値(2進法で表すと10000000~11111111)をマイナスであるとJavaが認識するからです。

また、「sample.jpg」は5.9キロバイトのファイルであるため、ファイルのデータを全て読み取るまでに1024バイト=1キロバイト分の情報を格納するdata配列が6回出力されます。


DataInputStream / DataOutputStream

DataInputStream

DataInputStreamは、入力ストリームからプリミティブ型のJavaデータを簡単に読み取るためのクラスです。

コンストラクタの引数には、InputStreamクラスを継承したクラス(FileInputStreamクラスなど)を取ります。

//コンストラクタにInputStreamクラスのサブクラスを渡します。
FileInputStream fis = new FileInputStream("ファイルのパス");
DataInputStream dis  new DataInputStream(fis);

また、readInt()、readBoolean()など、入力ストリームからプリミティブ型のJavaデータを読み取るためのメソッドが実装されています。


DataOutputStream

DataOutputStreamは、出力ストリームにプリミティブ型のJavaデータを他のJavaアプリケーションなどでも開けるような形で書き込むためのクラスです。

コンストラクタの引数には、OutputStreamクラスを継承したクラス(FileInputStreamクラスなど)を取ります。

//コンストラクタにOutputStreamクラスのサブクラスを渡します。
FileOutputStream fos = new FileOutputStream("ファイルのパス");
DataOutputStream bos  new BufferedOutputStream(fos);

また、writeInt(int v)、writeBoolean(boolean v)など、出力ストリームにプリミティブ型のJavaデータを書き込むためのメソッドが実装されています。


DataInputStream / DataOutputStreamの実行例

まずDataOutputStreamを使って、プリミティブ型のJavaデータを書き込んだ「sample.bin」を作成し、その後DataInputStreamを使って「sample.bin」のプリミティブ型のJavaデータを読み取るコードを実行して、DataInputStreamとDataOutputStreamの挙動を確認します。


DataOutputStreamのサンプルコード

まず、下記のコードでプリミティブ型のJavaデータを書き込んだ「sample.bin」を作成します。

import java.io.DataOutputStream;
import java.io.FileOutputStream;

public class DataOutputStreamSample {
	public static void main(String[] args) {
		//出力先ファイルを指定します。拡張子はバイナリファイルであることを表す「.bin」にしています。
		try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(".\\sample.bin"))) {

			//それぞれint型、double型、boolean型のJavaデータを.binファイルに書き込んでいます。
			dos.writeInt(12345);
			dos.writeDouble(3.14);
			dos.writeBoolean(true);

			//String型を書き込むには、writeUTF(String s)メソッドを使います。
			dos.writeUTF("Works");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}


DataOutputStreamのサンプルコードの実行結果

FileOutputStreamのコンストラクタに渡したパスに新たに「sample.bin」が出力されたことが確認できます。試しにメモ帳などのテキストエディタで「sample.bin」を開いてみると、著者の環境では以下のように表示されます。

  09@	�Q� Works

「sample.bin」ファイルの内容が文字から読み取れるようにはなっていないのがわかると思います。


DataInputStreamのサンプルコード

下記のコードで、上記のコードで作成した「sample.bin」の、プリミティブ型のJavaデータを読み取ります。

import java.io.DataInputStream;
import java.io.FileInputStream;

public class DataInputStreamSample {
	public static void main(String[] args) {
		//先ほど作成した「sample.bin」ファイルを読み込むようにします。
		try (DataInputStream dis = new DataInputStream(new FileInputStream(".\\sample.bin"))) {

			//入力ストリームの先頭から、それぞれのプリミティブ型に対応したメソッドを使って、
			//適切な型の変数にデータを格納します。
			int a = dis.readInt();
			double b = dis.readDouble();
			boolean c = dis.readBoolean();

			//String型のデータを格納するには、readUTF()メソッドを使います。
			String d = dis.readUTF();

			System.out.println(a + " " + b + " " + c + " " + d);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}


DataInputStreamのサンプルコードの実行結果

コンソールに以下のように出力されます。

12345 3.14 true Works

「sample.bin」ファイルから、プリミティブ型のJavaデータを読み取れたことが確認できると思います。


バイナリファイルに追記するには

DataInputStream / DataOutputStreamで使った「sample.bin」を例に、Javaにおいてバイナリファイルの追記を行う方法について説明します。


バイナリファイルに追記するサンプルコード

「sample.bin」に新たにlong型の数値とString型の文字列を追記してみます。

import java.io.DataOutputStream;
import java.io.FileOutputStream;

public class DataOutputStreamAppendSample {
	public static void main(String[] args) {
		//第一引数に出力先ファイルのパスを指定し、第二引数に追記することを表すtrueを渡します。
		try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(".\\sample.bin", true))) {
			dos.writeLong(9876543210L);
			dos.writeUTF("Hello, World!");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}


追記されたバイナリファイルを読み取るサンプルコード

上記のコードを実行した後に実行してください。

import java.io.DataInputStream;
import java.io.FileInputStream;

public class DataInputStreamAppendSample {
	public static void main(String[] args) {
		try (DataInputStream dis = new DataInputStream(new FileInputStream(".\\sample.bin"))) {

			int a = dis.readInt();
			double b = dis.readDouble();
			boolean c = dis.readBoolean();
			String d = dis.readUTF();
			long e = dis.readLong();
			String f = dis.readUTF();

			System.out.println(a + " " + b + " " + c + " " + d + " " + e + " " + f);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}


追記されたバイナリファイルを読み取るサンプルコードの実行結果

コンソールに以下のように出力されます。

12345 3.14 true Works 9876543210 Hello, World!

追記したJavaデータを読み取れたことが確認できました。


まとめ

今回はJavaにおけるバイナリファイルの入出力についての説明を行いました。同じような名前のクラスが多く、最初は戸惑うこともあるかもしれませんが、中身を紐解けば今まで学習してきた基礎の積み重ねです。もしつまづくことがあれば、以下の関連記事を読んで復習をしてみるのも良いでしょう。


関連記事:

【Java】try - with - resources 構文

【Java】入出力ストリーム(java.ioパッケージ)

【Java】 シリアライズの紹介!

【Java】テキストファイルの入出力



【著者】

ゆうさい

フォワードソフト株式会社のエンジニア。経験はまだ浅いものの、Java、Python、JavaScriptなど様々な言語の他、クラウドやネットワーク技術を勉強しています。PythonやVBAを使った自動化で楽をするのを考えるのが好きです。 Oracle Certified Java Programmer Gold SE 11、HTML5プロフェッショナル認定試験レベル2、AWSプラクティショナーなどの情報資格を保有しています。

よく読まれている記事
【Java】JSPでタグライブラリを使う(JSTL)

【Java】JSPでタグライブラリを使う(JSTL)

こんにちは。エンジニアの新田です!ここでは、システムエンジニアとして働いている私が、システム開発手法や開発言語について紹介していこうと思います。今回は、JSPの標準タグライブラリ「JSTL」について紹介します。Javaについて勉強している方、Webアプリケーションを構築したいと思っている方の参考になれば幸いです!関連記事リンク: 【Java】JSPの基本的な構文/【Java】JSPのアクションタグ

【Java】Stringクラス文字列を操作するメソッドの使い方まとめ!実例も紹介!

【Java】Stringクラス文字列を操作するメソッドの使い方まとめ!実例も紹介!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!そもそもStringとは何?『 String 』... Java言語において文字列のデータ型を指します。基本デ

【Java】文字列の置き換え(String#format)!エスケープシーケンスのまとめも!!

【Java】文字列の置き換え(String#format)!エスケープシーケンスのまとめも!!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!Stringクラスformatメソッドの文字列整形【java.utilパッケージ】Formatterクラスfo

【Java】文字列格納後に変更可能!?StringBufferクラスとStringBuilderクラス!

【Java】文字列格納後に変更可能!?StringBufferクラスとStringBuilderクラス!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者にも✅、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!文字列を扱う3つのクラス【java.langパッケージ】java.langパッケージの文字列を扱うクラスにはS

【Java】値?変数?型??しっかり解説!『データ型(プリミティブ型と参照型)』

【Java】値?変数?型??しっかり解説!『データ型(プリミティブ型と参照型)』

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!プリミティブ型と参照型プログラム開発では型を持った変数を使ってデータのやり取りをしますが、データ型によって仕様