javaはファイルサイズを効率的に取得します


166

グーグルしながら、私はそれを使用して java.io.File#length()が遅くなることがあります。 FileChannel持っているsize()だけでなく、利用可能である方法を。

Javaでファイルサイズを取得する効率的な方法はありますか?


7
File.length()は「遅くなる可能性がある」というリンクを提供できますか?
matt b

1
申し訳ありませんが、ここにリンクjavaperformancetuning.com/tips/rawtips.shtmlの 検索があります。それは本当に紛らわしいステートメントです、それはそれがシステムコールであるとほとんど仮定されているようです。
joshjdevl 2008

25
ファイル長を取得するには、どのように実行してもシステムコールが必要です。ネットワークまたはその他の非常に遅いファイルシステムを使用している場合は、速度が遅くなる可能性があります。File.length()よりも速く取得する方法はありません。ここでの「低速」の定義は、不必要に呼び出さないことを意味します。
jsight 2008

それがGHadが以下でテストしようとしていたことだと思います。私の結果は(ubuntu 8.04の場合)です。1つのアクセスURLだけが最速です。5回の実行、50回の反復CHANNELは、最も速く混乱していますか?:)しかし、私の目的のために、私は1つのアクセスを行うだけです。変なのに?結果が異なる
joshjdevl 2008

1
情報がキャッシュ内ではなくディスク上にある場合、この操作は非常に遅くなる可能性があります。(1000倍遅い)しかし、必要な情報が常にキャッシュにあることを確認する以外に、これについてできることはほとんどありません(事前にロードして十分なメモリを確保してメモリに保持するなど)
Peter Lawrey

回答:


102

さて、私は以下のコードでそれを測定しようとしました:

実行数= 1および反復数= 1の場合、URLメソッドが最も速く、その後にチャネルが続きます。私はこれを約10回新鮮な一時停止で実行します。したがって、一度のアクセスでは、URLを使用することが、私が考えることができる最も速い方法です。

LENGTH sum: 10626, per Iteration: 10626.0

CHANNEL sum: 5535, per Iteration: 5535.0

URL sum: 660, per Iteration: 660.0

実行= 5と反復= 50の場合、画像の描画は異なります。

LENGTH sum: 39496, per Iteration: 157.984

CHANNEL sum: 74261, per Iteration: 297.044

URL sum: 95534, per Iteration: 382.136

ファイルはファイルシステムへの呼び出しをキャッシュする必要がありますが、チャネルとURLにはある程度のオーバーヘッドがあります。

コード:

import java.io.*;
import java.net.*;
import java.util.*;

public enum FileSizeBench {

    LENGTH {
        @Override
        public long getResult() throws Exception {
            File me = new File(FileSizeBench.class.getResource(
                    "FileSizeBench.class").getFile());
            return me.length();
        }
    },
    CHANNEL {
        @Override
        public long getResult() throws Exception {
            FileInputStream fis = null;
            try {
                File me = new File(FileSizeBench.class.getResource(
                        "FileSizeBench.class").getFile());
                fis = new FileInputStream(me);
                return fis.getChannel().size();
            } finally {
                fis.close();
            }
        }
    },
    URL {
        @Override
        public long getResult() throws Exception {
            InputStream stream = null;
            try {
                URL url = FileSizeBench.class
                        .getResource("FileSizeBench.class");
                stream = url.openStream();
                return stream.available();
            } finally {
                stream.close();
            }
        }
    };

    public abstract long getResult() throws Exception;

    public static void main(String[] args) throws Exception {
        int runs = 5;
        int iterations = 50;

        EnumMap<FileSizeBench, Long> durations = new EnumMap<FileSizeBench, Long>(FileSizeBench.class);

        for (int i = 0; i < runs; i++) {
            for (FileSizeBench test : values()) {
                if (!durations.containsKey(test)) {
                    durations.put(test, 0l);
                }
                long duration = testNow(test, iterations);
                durations.put(test, durations.get(test) + duration);
                // System.out.println(test + " took: " + duration + ", per iteration: " + ((double)duration / (double)iterations));
            }
        }

        for (Map.Entry<FileSizeBench, Long> entry : durations.entrySet()) {
            System.out.println();
            System.out.println(entry.getKey() + " sum: " + entry.getValue() + ", per Iteration: " + ((double)entry.getValue() / (double)(runs * iterations)));
        }

    }

    private static long testNow(FileSizeBench test, int iterations)
            throws Exception {
        long result = -1;
        long before = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            if (result == -1) {
                result = test.getResult();
                //System.out.println(result);
            } else if ((result = test.getResult()) != result) {
                 throw new Exception("variance detected!");
             }
        }
        return (System.nanoTime() - before) / 1000;
    }

}

1
XPとlinuxのどちらでも、URLの方法がシングルアクセスに最適な方法のようです。Greetz GHad
GHad

73
stream.available()ファイルの長さは返しません。他のストリームをブロックせずに読み取りに使用できるバイト数を返します。ファイルの長さと同じバイト数である必要はありません。ストリームから実際の長さを取得するには、実際にそれを読み取る必要があります(その間、読み取りバイトをカウントします)。
BalusC 2009年

11
このベンチマークは、またはその解釈が正しくありません。反復回数が少ない場合、後のテストではオペレーティングシステムのファイルキャッシングを利用します。より高い反復テストでは、ランク付けは正しいが、File.length()が何かをキャッシュしているためではなく、他の2つのオプションが同じメソッドに基づいているが、速度を低下させる余分な作業を行うためです。
x4u

2
@Paolo、ファイルシステムアクセスのキャッシングと最適化は、OSの主要な責任の1つです。faqs.org/docs/linux_admin/buffer-cache.html良好なベンチマーク結果を得るには、実行する前にキャッシュをクリアする必要があります。
z0r 2012

3
InputStream.available()のjavadocが言っていることを超えて、available()メソッドがintを返すという事実は、URLアプローチに対する赤旗であるべきです。3GBのファイルで試してみてください。ファイルの長さを決定する有効な方法ではないことは明らかです。
スクラビー2014年

32

GHadによって与えられたベンチマークは、長さを取得する以外にも、他の多くの要素(反射、インスタンス化など)を測定します。これらのものを取り除こうとすると、1回の呼び出しで次の時間がマイクロ秒で取得されます。

   ファイル合計___ 19.0、反復ごとに___ 19.0
    raf sum ___ 16.0、反復ごとに___ 16.0
チャネルsum__273.0、Iteration__273.0ごと

100回の実行と10000回の反復の場合、次のようになります。

   Iteration__1.7676290000000001あたりのファイルsum__1767629.0
    raf sum ___ 881284.0、Iteration__0.8812840000000001ごと
チャネルsum ___ 414286.0、Iteration__0.414286ごと

100MBのファイルの名前を引数として指定して、次の変更されたコードを実行しました。

import java.io.*;
import java.nio.channels.*;
import java.net.*;
import java.util.*;

public class FileSizeBench {

  private static File file;
  private static FileChannel channel;
  private static RandomAccessFile raf;

  public static void main(String[] args) throws Exception {
    int runs = 1;
    int iterations = 1;

    file = new File(args[0]);
    channel = new FileInputStream(args[0]).getChannel();
    raf = new RandomAccessFile(args[0], "r");

    HashMap<String, Double> times = new HashMap<String, Double>();
    times.put("file", 0.0);
    times.put("channel", 0.0);
    times.put("raf", 0.0);

    long start;
    for (int i = 0; i < runs; ++i) {
      long l = file.length();

      start = System.nanoTime();
      for (int j = 0; j < iterations; ++j)
        if (l != file.length()) throw new Exception();
      times.put("file", times.get("file") + System.nanoTime() - start);

      start = System.nanoTime();
      for (int j = 0; j < iterations; ++j)
        if (l != channel.size()) throw new Exception();
      times.put("channel", times.get("channel") + System.nanoTime() - start);

      start = System.nanoTime();
      for (int j = 0; j < iterations; ++j)
        if (l != raf.length()) throw new Exception();
      times.put("raf", times.get("raf") + System.nanoTime() - start);
    }
    for (Map.Entry<String, Double> entry : times.entrySet()) {
        System.out.println(
            entry.getKey() + " sum: " + 1e-3 * entry.getValue() +
            ", per Iteration: " + (1e-3 * entry.getValue() / runs / iterations));
    }
  }
}

3
実際、あなたはそれが他の側面を測定していると言っているのは正しいですが、私の質問ではもっと明確にする必要があります。複数のファイルのファイルサイズを取得したいと考えています。可能な限り迅速な方法が必要です。オブジェクトの作成とオーバーヘッドを考慮する必要があります。これは実際のシナリオです
joshjdevl

3
時間の約90%は、そのgetResourceに費やされます。Javaバイトコードを含むファイルの名前を取得するためにリフレクションを使用する必要があるとは思いません。

20

この投稿のすべてのテストケースは、テストされた各メソッドの同じファイルにアクセスするため、欠陥があります。したがって、ディスクキャッシングは、テスト2と3のメリットを生かします。私のポイントを証明するために、GHADによって提供されたテストケースを取り、列挙の順序を変更しました。結果は以下のとおりです。

結果を見ると、File.length()が本当に勝者だと思います。

テストの順序は出力の順序です。私のマシンでかかった時間は実行間で異なりますが、最初でないときはFile.Length()が表示され、最初のディスクアクセスが発生したことがわかります。

---
LENGTH sum: 1163351, per Iteration: 4653.404
CHANNEL sum: 1094598, per Iteration: 4378.392
URL sum: 739691, per Iteration: 2958.764

---
CHANNEL sum: 845804, per Iteration: 3383.216
URL sum: 531334, per Iteration: 2125.336
LENGTH sum: 318413, per Iteration: 1273.652

--- 
URL sum: 137368, per Iteration: 549.472
LENGTH sum: 18677, per Iteration: 74.708
CHANNEL sum: 142125, per Iteration: 568.5

9

リソースではなく絶対パスでアクセスされるファイルを使用するようにコードを変更すると、異なる結果が得られます(1回の実行、1回の反復、および100,000バイトのファイル-10バイトのファイルの時間は100,000バイトと同じです) )

長さの合計:33、反復ごと:33.0

チャネル合計:3626、反復ごと:3626.0

URLの合計:294、反復ごと:294.0


9

これらのクラスはファイルを読み取るためにストリームを開くため、rgrigのベンチマークに応じて、FileChannelとRandomAccessFileインスタンスの開閉にかかる時間も考慮する必要があります。

ベンチマークを変更した後、85MBファイルで1回の反復でこれらの結果を得ました。

file totalTime: 48000 (48 us)
raf totalTime: 261000 (261 us)
channel totalTime: 7020000 (7 ms)

同じファイルを10000回繰り返した場合:

file totalTime: 80074000 (80 ms)
raf totalTime: 295417000 (295 ms)
channel totalTime: 368239000 (368 ms)

必要なのがファイルサイズだけの場合は、file.length()がそれを行う最も速い方法です。読み取り/書き込みなどの他の目的でファイルを使用する場合は、RAFの方が適しているようです。ファイル接続を閉じることを忘れないでください:-)

import java.io.File;
import java.io.FileInputStream;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Map;

public class FileSizeBench
{    
    public static void main(String[] args) throws Exception
    {
        int iterations = 1;
        String fileEntry = args[0];

        Map<String, Long> times = new HashMap<String, Long>();
        times.put("file", 0L);
        times.put("channel", 0L);
        times.put("raf", 0L);

        long fileSize;
        long start;
        long end;
        File f1;
        FileChannel channel;
        RandomAccessFile raf;

        for (int i = 0; i < iterations; i++)
        {
            // file.length()
            start = System.nanoTime();
            f1 = new File(fileEntry);
            fileSize = f1.length();
            end = System.nanoTime();
            times.put("file", times.get("file") + end - start);

            // channel.size()
            start = System.nanoTime();
            channel = new FileInputStream(fileEntry).getChannel();
            fileSize = channel.size();
            channel.close();
            end = System.nanoTime();
            times.put("channel", times.get("channel") + end - start);

            // raf.length()
            start = System.nanoTime();
            raf = new RandomAccessFile(fileEntry, "r");
            fileSize = raf.length();
            raf.close();
            end = System.nanoTime();
            times.put("raf", times.get("raf") + end - start);
        }

        for (Map.Entry<String, Long> entry : times.entrySet()) {
            System.out.println(entry.getKey() + " totalTime: " + entry.getValue() + " (" + getTime(entry.getValue()) + ")");
        }
    }

    public static String getTime(Long timeTaken)
    {
        if (timeTaken < 1000) {
            return timeTaken + " ns";
        } else if (timeTaken < (1000*1000)) {
            return timeTaken/1000 + " us"; 
        } else {
            return timeTaken/(1000*1000) + " ms";
        } 
    }
}

8

私はこれと同じ問題に遭遇しました。ファイルサイズとネットワーク共有上の90,000ファイルの変更日を取得する必要がありました。Javaを使用し、可能な限り最小限に抑えるには、非常に長い時間がかかります。(ファイルからURLとオブジェクトのパスも取得する必要がありました。そのため多少異なりますが、1時間以上です。)次に、ネイティブのWin32実行可能ファイルを使用して同じタスクを実行し、ファイルをダンプしました。パス、変更、サイズをコンソールに設定し、Javaから実行します。スピードはすごかった。ネイティブプロセス、およびデータを読み取るための私の文字列処理では、1秒あたり1000を超えるアイテムを処理できます。

したがって、人々は上記のコメントをランクダウンしましたが、これは有効な解決策であり、私の問題を解決しました。私の場合、必要なサイズのフォルダーを事前に知っていて、それをコマンドラインでwin32アプリに渡すことができました。ディレクトリの処理に数時間から数分かかりました。

この問題は、Windows固有の問題のようにも思われました。OS Xには同じ問題がなく、OSができるのと同じ速さでネットワークファイル情報にアクセスできました。

WindowsでのJavaファイルの処理はひどいものです。ただし、ファイルのローカルディスクアクセスは問題ありません。ひどいパフォーマンスを引き起こしたのはネットワーク共有だけでした。Windowsはネットワーク共有に関する情報を取得し、1分もかからずに合計サイズを計算できます。

-ベン


3

ディレクトリ内の複数のファイルのファイルサイズが必要な場合は、を使用しますFiles.walkFileTree。サイズはBasicFileAttributesお手元に届いたものから入手できます。

これは.length()、の結果を呼び出すFile.listFiles()か、の結果を使用するよりもはるかに高速Files.size()ですFiles.newDirectoryStream()。私のテストケースでは、約100倍高速でした。


Files.walkFileTree参考までに、Android 26 以降で利用できます。
Joshua Pinter

2

実際、「ls」の方が速いかもしれません。ファイル情報の取得を処理するJavaには間違いなくいくつかの問題があります。残念ながら、Windowsに対応する安全な再帰lsの方法はありません。(cmd.exeのDIR / Sは混乱し、無限ループでエラーを生成する可能性があります)

XPでLAN上のサーバーにアクセスすると、Windowsでフォルダー内のファイル数(33,000)と合計サイズを取得するのに5秒かかります。

これをJavaで再帰的に繰り返すと、5分以上かかります。file.length()、file.lastModified()、file.toURI()を実行するのにかかる時間の測定を開始しました。私の時間の99%がこれらの3つの呼び出しに費やされていることがわかりました。私が実際に行う必要がある3つの呼び出し...

1000ファイルの違いは、ローカルでは15ミリ秒、サーバーでは1800ミリ秒です。Javaでのサーバーパススキャンは、途方もなく遅いです。ネイティブOSが同じフォルダを高速にスキャンできるのに、Javaができないのはなぜですか?

より完全なテストとして、XPでWineMergeを使用して、変更された日付と、サーバー上のファイルとローカルのファイルのサイズを比較しました。これは、各フォルダー内の33,000ファイルのディレクトリツリー全体を繰り返し処理していました。合計時間、7秒。java:5分以上。

したがって、OPからの元のステートメントと質問は真実であり、有効です。ローカルファイルシステムを処理する場合は、あまり目立ちません。33,000アイテムのフォルダーをローカルで比較するには、WinMergeでは3秒、Javaではローカルで32秒かかります。繰り返しになりますが、Javaとネイティブは、これらの基本的なテストで10倍のスローダウンです。

Java 1.6.0_22(最新)、ギガビットLAN、およびネットワーク接続、pingは1ミリ秒未満(両方とも同じスイッチ内)

Javaは遅いです。


2
これもOS固有のようです。sambaを使用してOS Xから同じフォルダーの後に同じJavaアプリを実行すると、33,000のアイテム、サイズ、日付全体をリストするのに26秒かかりました。それでは、ネットワークJavaはWindowsでは遅いのでしょうか。(OS Xはjava 1.6.0_22でもありました。)
Ben Spink

2

GHadのベンチマークから、人々が言及したいくつかの問題があります:

1>前述のBalusCのように:この場合、stream.available()がフローされます。

なぜなら、available()は、この入力ストリームのメソッドの次の呼び出しによってブロックされることなく、この入力ストリームから読み取ることができる(またはスキップできる)推定バイト数を返すからです。

したがって、最初にこのアプローチをURLから削除します。

2> StuartHが述べたように-テストの実行順序によってもキャッシュが異なるため、テストを個別に実行することでそれを排除します。


次にテストを開始します。

CHANNEL 1が単独で実行される場合:

CHANNEL sum: 59691, per Iteration: 238.764

LENGTHが単独で実行される場合:

LENGTH sum: 48268, per Iteration: 193.072

LENGTHの方が勝者のようです:

@Override
public long getResult() throws Exception {
    File me = new File(FileSizeBench.class.getResource(
            "FileSizeBench.class").getFile());
    return me.length();
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.