検索

キーワード


【Java】FileChannelを使って高速にファイル入出力を行ってみた

  • 公開日:2021-05-02 23:31:38
  • 最終更新日:2021-05-02 23:38:18
【Java】FileChannelを使って高速にファイル入出力を行ってみた

当記事では、java.nio.fileパッケージ内のFileChannelクラスを用いて高速にファイルを入出力する方法について、Java SE 11 Goldを取得している著者が解説します。


java.nio.fileパッケージの概要について知りたい方は、以下の記事を参照して下さい。

【Java】Pathオブジェクトを生成する方法(java.nio.fileパッケージ)

また、当記事で扱う、ファイルの入出力関連するクラスについては以下の記事で解説します。

【Java】バイナリファイルの入出力

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


FileChannelクラスとは?

nioパッケージには、Channelというインターフェースがあります。英語の「channel」という単語には「水路、運河」などの意味があり、プログラミングにおいては「データの通り道」といった意味合いを持ちます。

FileChannelクラスは、ファイルの入出力に特化して実装されたクラスで、ファイルの通り道を表すだけのクラスです。

そのため、Reader/WriterやinputStream/OutputStreamと違ってクラス自体には入力や出力といったデータの向きの概念がなく、メソッドを使ってファイルの入出力を行います。


FileChannelクラスの使い方

FileInputStreamクラスなどに実装されているgetChannelメソッドを使ってFIleChannelクラスを取得します。

他の入出力に使うクラス同様、メモリリークを防ぐためにtry-with-resource構文を用いるなどして必ずcloseするようにしましょう。


FileChannelクラスのメソッド

FileChannelクラスは、主に以下のようなメソッドを持ちます。

メソッド名返り値の型説明
read(ByteBuffer dst[,long position])
intチャネルから読み取った内容を、dstで指定したバッファに書き込みます。
返り値は書き込んだバイト数です。
positionで書き込み位置を指定することも出来ます。
write(ByteBuffer src[,long position])
intsrcで指定したバッファの内容を、チャネルに書き込みます。
返り値は書き込んだバイト数です。
positionで書き込み位置を指定することも出来ます。
size()longチャネルの現在のサイズを返します。


FileChannelクラスを使った高速な入出力

以下の2つのコードでは、86MBほどのファイルを生成したのち、そのファイルの読み込みと別ファイルへの出力を行い、時間を計測して実行速度を比較しています。

BufferedWriterなどのクラスの説明は、以下の記事で行っています。

【Java】バイナリファイルの入出力

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


FileChannelを使わずにファイル入出力を行うサンプルコード

まずは、BufferedReaderとBufferedWriterを用いてファイルの入出力を行うプログラムを実行してみます。

public class FileIOSample {
	public static void main(String[] args) {
		try {

			//作成するファイルのパスを指定
			File file = new File("./FileChannelSample.txt");

			//BufferedWriterで指定したファイルに書き込む
			try (BufferedWriter tmp = new BufferedWriter(new FileWriter(file))) {

				//86MB程度のファイルを生成
				for (int i = 0; i < 10000000; i++) {
					tmp.write("" + i);
					tmp.newLine();
				}

			} catch (Exception e) {
				e.printStackTrace();
			}

			//ファイル入出力の処理時間計測を開始
			long startTime = new Date().getTime();

			//読み込み用と書き込み用のバッファを生成
			try (BufferedReader br = new BufferedReader(new FileReader(file));
					BufferedWriter bw = new BufferedWriter(new FileWriter("./FileChannelSample2.txt"))) {

				//行を格納するための変数を宣言
				String line;

				//読み込む行がなくなるまで読み込む
				while ((line = br.readLine()) != null) {

					//一行ずつ書き込みを行う
					bw.write(line);
					bw.newLine();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}

			//ファイル入出力の処理時間計測結果を出力
			System.out.println("実行時間: " + ((long) new Date().getTime() - startTime) + "ms");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

このサンプルコードの実行時間は、以下のようになります。

実行時間: 801ms


FileChannelを使ってファイル入出力を行うサンプルコード

次に、FileChannelを使ったファイル入出力の速度を測定してみます。

public class FileChannelSample {
	public static void main(String[] args) {
		try {

			//作成するファイルのパスを指定
			File file = new File("./FileChannelSample.txt");

			//BufferedWriterで指定したファイルに書き込む
			try (BufferedWriter tmp = new BufferedWriter(new FileWriter(file))) {

				//86MB程度のファイルを生成
				for (int i = 0; i < 10000000; i++) {
					tmp.write("" + i);
					tmp.newLine();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}

			//ファイル入出力の処理時間計測を開始
			long startTime = new Date().getTime();

			//読み込み用と書き込み用のストリームからそれぞれチャネルを取得
			try (FileInputStream fis = new FileInputStream(file);
					FileOutputStream fos = new FileOutputStream("./FileChannelSample2.txt");
					FileChannel fileChannelInput = fis.getChannel();
					FileChannel fileChannelOutput = fos.getChannel()) {

				//ByteBufferを生成する
				//allocateDirectメソッドで、intで指定した数値分の容量を持ったバッファを生成
				ByteBuffer bf = ByteBuffer.allocateDirect((int) fileChannelInput.size());

				//clearメソッドでバッファの内容を初期化したのち、
				//読み込み用のストリームから生成したチャネルからデータを読み込む
				bf.clear();
				fileChannelInput.read(bf);

				//flipメソッドでバッファの内容を反転したのち(この処理がないと読み込んだ順とは逆順に書き込まれる)、
				//書き込み用のストリームから生成したチャネルにデータを書き込む
				bf.flip();
				fileChannelOutput.write(bf);

			} catch (Exception e) {
				e.printStackTrace();
			}

			//ファイル入出力の処理時間計測結果を出力
			System.out.println("実行時間: " + ((long) new Date().getTime() - startTime) + "ms");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

このサンプルコードの実行時間は、以下のようになります。

実行時間: 125ms

このように、少々コードの複雑さは増しますがFileChannelを使うことで大幅にファイル入出力の時間を短縮することが出来ます。


まとめ

ファイル入出力は他の処理に比べて時間がかかりやすく、プログラムの実行速度上ボトルネックになりやすいところでもあります。

ここでは紹介しませんでしたが、FileChannelには並行処理には欠かせないファイルの排他制御にまつわるメソッドなど便利なメソッドが実装されていますので、興味があったら調べてみてください。


関連記事:

【Java】java.nio.fileパッケージ(NIO.2)

【Java】Pathオブジェクトを生成する方法(java.nio.fileパッケージ)

【Java】java.nio.fileパッケージの使用例①(ファイルの作成、存在チェック)

【Java】java.nio.fileパッケージの使用例②(移動・コピー、削除)

【Java】java.nio.fileパッケージの使用例③(ファイル属性の取得・変更)

【Java】java.io.Fileクラスの使い方(ファイル・ディレクトリ操作、パス、ファイル名、拡張子)

【Java】java.io.Fileクラスの使い方(ファイル一覧の取得、フィルタ)

【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】値?変数?型??しっかり解説!『データ型(プリミティブ型と参照型)』

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