170

グーグルをしていると、使用java.io.File#length()が遅くなることがあります。 FileChannel同様に利用可能なsize()方法があります。

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

4

9 に答える 9

103

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

実行=1および反復=1の場合、URLメソッドが最も速く、次にチャネルが続きます。私はこれを約10回新鮮な一時停止で実行します。したがって、1回限りのアクセスでは、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;
    }

}
于 2008-09-22T19:21:05.380 に答える
32

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

   Iteration___19.0ごとのファイルsum___19.0
    raf sum ___ 16.0、反復ごと___ 16.0
チャネルsum__273.0、反復ごと__273.0

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

   ファイルsum__1767629.0、反復ごと__1.7676290000000001
    raf sum ___ 881284.0、反復ごと__0.8812840000000001
チャネルsum___414286.0、反復ごと__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));
    }
  }
}
于 2008-09-23T06:18:54.503 に答える
21

この投稿のすべてのテスト ケースは、テストされた各メソッドに対して同じファイルにアクセスするため、欠陥があります。そのため、テスト 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
于 2011-03-22T01:02:59.260 に答える
10

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

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

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

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

于 2008-09-23T03:42:41.727 に答える
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";
        } 
    }
}
于 2009-11-26T13:18:49.197 に答える
8

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

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

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

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

--ベン

于 2011-04-02T03:25:53.730 に答える
3

ディレクトリ内の複数のファイルのファイル サイズが必要な場合は、 を使用しますFiles.walkFileTree。からサイズを取得できBasicFileAttributesます。

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

于 2014-01-23T12:00:23.987 に答える
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 (最新)、Gigabit LAN、およびネットワーク接続、ping は 1 ミリ秒未満 (両方とも同じスイッチ内)

Javaは遅いです。

于 2010-11-17T07:40:25.530 に答える