ホーム   »  スポンサー広告  »     »  Java  »  ファイルのコピー

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

ファイルのコピー

Java でのファイルのコピーは自前で実装するのが定石と言われていた。実際 BugParade でも File クラスにファイルコピーのメソッドは不要と言う形で close されている。

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4032604
It doesn't, and we have no plans to add such a method because (1) You can easily do this yourself, and (2) Its behavior would vary widely across platforms.

そんなのは簡単に作れるし、プラットフォームごとに挙動が異なるから、ということだ。しかし良く見るとこの回答がされたのは JDK 1.1 の頃であり、実は JDK 1.4 で追加された New I/O の?FileChannel#transferFrom()?/ FileChannel#transferTo() を使えば簡単かつ高速にファイルのコピーを行うことが出来る。

◆ 実装方法の比較

まずは古き良き時代の方法を見てよう。コピー元と先の入出力ストリームを開いて、自前で用意したバッファを経由してループしながら逐次書き込みを行う (ファイルクローズは省略)。

バッファコピー
byte[] buffer = new byte[bufferSize];
int len;
InputStream in = new FileInputStream(file1);
OutputStream out = new FileOutputStream(file2);
while((len = in.read(buffer)) >= 0){
??? out.write(buffer, 0, len);
}

オーソドックスではあるが、コピー用の最適なバッファサイズはコピーするファイルサイズに依存する。普段はあまり考えず 4096?や 1024 などと指定している人も多いだろう。

これが New I/O を使用した方法だと以下のようになる。両チャネルを開いて転送という感じだ。

New I/O コピー

FileChannel src = new FileInputStream(file1).getChannel();
FileChannel dst = new FileOutputStream(file2).getChannel();
dst.transferFrom(src, 0, src.size());

ちなみに、Windows 環境で最も理想的な方法は Win32 API の CopyFile() 関数を直接呼び出すことだ。

◆ パフォーマンス測定と考察

以下の4パターンについてのパフォーマンスを比較した。

  1. C 言語で Win32?の CopyFile() を直接実行
  2. 1kB のバッファを用いたループでコピー
  3. 1MB のバッファを用いたループでコピー
  4. FileChannel#transferFrom() でコピー

20#x301C229B (1B#x301C500MB) までのデータファイルをコピー用に用意し、それぞれの方法に対する所要時間を測定する。ただし、平均や標準誤差を取るのが面倒だったので各一回ずつの測定。その分ファイルサイズの幅を細かくしたので平均は目視で。

X軸 Y軸共に対数グラフになっているので見方に注意。なお 0[msec] の計測値はグラフの都合上 1[msec] に修正してある。?

このグラフでは New I/O、Win32API、1MB バッファのループがほぼ同じ推移を見せている。だが一般的なプログラミングではコピー用のバッファに 1MB というのは大きすぎる場合が多く、バッファサイズを調整しなくても Win32 API と同等のパフォーマンスが得られる?New I/O?を用いるのが現実的にベターと思われる。

逆にファイルサイズが想定されているならバッファサイズを調整してより省メモリで高パフォーマンスを目指す実装も良いだろう。

ちなみに、1kB バッファでは?103B 付近から、1MBバッファでは106B 付近から実行コストが指数関数的に増加していることがグラフから見て取れる。

◆ 宣言を調べる

ところでこの FileChannel#transferFrom() はどういう実装なのだろうか? JDK 1.4 のソースを紐解いてみると abstract 宣言されている。挙動の類似から内部で Native メソッドを経由して Win32 API を利用していると思われるのだが、実装クラスでどういう宣言がなされているのか、リフレクションを使って調べてみよう。

かなり手抜きで申し訳ないが以下のようなコードを作成した所:

System.out.println(
???
new FileOutputStream("a.txt").getChannel().getClass()
???
.getMethod("transferFrom", new Class[]{
???????
ReadableByteChannel.class, Long.TYPE, Long.TYPE
??? })
);

残念ながらこの実装メソッド自体には native 宣言がさているわけではないようだ

public long sun.nio.ch.FileChannelImpl.transferFrom(java.nio.channels.ReadableByteChannel,long,long)
???
throws java.io.IOException?

だが FileChannelImpl クラスで宣言されている全メソッドをリフレクションで調べてみると、以下のような native メソッドが定義されているのが分かる。内部でこのメソッドを経由して CopyFile() が実行されているのだろう。

private native long sun.nio.ch.FileChannelImpl.transferTo0(int,long,long,int)?

◆ 結論

特に理由がなければファイルのコピーは New I/O で。

◇ 検証環境

JDK 1.5 + WinXP + IA32
 java version "1.5.0_05"
  Java™ 2 Runtime Environment, Standard Edition (build 1.5.0_05-b05)
  Java HotSpot™ Client VM (build 1.5.0_05-b05, mixed mode, sharing)?
 Microsoft Windows XP Professional SP2?
 IBM ThinkPad X31 2672LJ7
  Intel Pentium M Processor?1.7GHz
  1GM PC-2700 DDR SDRAM, 80GB 4,200rpm 9.5mm HD

コメント
トラックバック
トラックバック URL
コメントの投稿
管理者にだけ表示を許可する
Profile
Takami Torao
Takami Torao
C/C++ 使いだった 1996年、運命の Java と出会い現在に至る。のらアーキテクト。
Yah, this is image so I don't wanna eat spam, sorry!
Search

Google
MOYO Laboratory
Web

カテゴリー
最近の記事
最近のコメント
最近のトラックバック
月別アーカイブ
ブロとも申請フォーム
RSSフィード
リンク
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。