java mendapatkan ukuran file secara efisien

166

Saat googling, saya melihat bahwa menggunakan java.io.File#length()bisa lambat. FileChannelmemiliki size()metode yang tersedia juga.

Apakah ada cara yang efisien di java untuk mendapatkan ukuran file?

joshjdevl
sumber
7
dapatkah Anda memberikan tautan yang mengatakan bahwa File.length () "bisa lambat"?
matt b
1
maaf, ini tautan javaperformancetuning.com/tips/rawtips.shtml mencari "Informasi file seperti File.length () memerlukan panggilan sistem dan bisa lambat." itu benar-benar pernyataan yang membingungkan, sepertinya hampir diasumsikan bahwa itu akan menjadi panggilan sistem.
joshjdevl
25
Untuk mendapatkan panjang file akan membutuhkan panggilan sistem tidak peduli bagaimana Anda melakukannya. Mungkin lambat jika melalui jaringan atau sistem file yang sangat lambat lainnya. Tidak ada cara yang lebih cepat untuk mendapatkannya daripada File.length (), dan definisi "lambat" di sini hanya berarti jangan menyebutnya tidak perlu.
jsight
Saya pikir itulah yang coba diuji GHad di bawah ini. Hasil saya adalah (Di ubuntu 8.04): hanya satu URL akses yang tercepat. 5 berjalan, 50 iterasi CHANNEL paling cepat membingungkan? :) untuk keperluan saya, saya hanya akan melakukan satu akses. meskipun itu aneh? bahwa kami mendapat hasil yang berbeda
joshjdevl
1
Operasi ini bisa sangat lambat jika informasi di disk daripada di cache. (seperti 1000x lebih lambat) namun, ada sedikit yang dapat Anda lakukan selain memastikan informasi yang Anda butuhkan selalu ada dalam cache (seperti pra memuatnya dan memiliki cukup memori sehingga tetap tersimpan dalam memori)
Peter Lawrey

Jawaban:

102

Baiklah, saya mencoba mengukurnya dengan kode di bawah ini:

Untuk menjalankan = 1 dan iterasi = 1 metode URL paling cepat diikuti oleh saluran. Saya menjalankan ini dengan jeda segar sekitar 10 kali. Jadi untuk akses satu kali, menggunakan URL adalah cara tercepat yang dapat saya pikirkan:

LENGTH sum: 10626, per Iteration: 10626.0

CHANNEL sum: 5535, per Iteration: 5535.0

URL sum: 660, per Iteration: 660.0

Untuk menjalankan = 5 dan iterasi = 50 gambarnya berbeda.

LENGTH sum: 39496, per Iteration: 157.984

CHANNEL sum: 74261, per Iteration: 297.044

URL sum: 95534, per Iteration: 382.136

File harus caching panggilan ke sistem file, sementara saluran dan URL memiliki beberapa overhead.

Kode:

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;
    }

}
GHad
sumber
1
Sepertinya cara URL adalah yang terbaik untuk akses tunggal apakah itu XP atau linux. Greetz GHad
GHad
73
stream.available()tidak mengembalikan panjang file. Ini mengembalikan jumlah byte yang tersedia untuk dibaca tanpa memblokir aliran lainnya. Jumlah byte tidak selalu sama dengan panjang file. Untuk mendapatkan panjang sebenarnya dari aliran, Anda benar-benar perlu membacanya (dan menghitung byte baca sementara itu).
BalusC
11
Tolok ukur ini atau lebih tepatnya interpretasinya tidak benar. Dalam hitungan iterasi yang rendah, pengujian selanjutnya memanfaatkan file caching dari sistem operasi. Dalam tes iterasi yang lebih tinggi peringkatnya benar tetapi bukan karena File.length () adalah caching sesuatu tetapi hanya karena 2 pilihan lainnya didasarkan pada metode yang sama tetapi melakukan pekerjaan tambahan yang memperlambatnya.
x4u
2
@ Paolo, caching dan mengoptimalkan akses sistem file adalah salah satu tanggung jawab utama OS. faqs.org/docs/linux_admin/buffer-cache.html Untuk mendapatkan hasil pembandingan yang baik, cache harus dihapus sebelum setiap kali dijalankan.
z0r
3
Di luar apa yang dikatakan javadoc untuk InputStream.available (), fakta bahwa metode available () mengembalikan sebuah int harus menjadi tanda bahaya terhadap pendekatan URL. Cobalah dengan file 3GB dan akan jelas bahwa itu bukan cara yang valid untuk menentukan panjang file.
Scrubbie
32

Benchmark yang diberikan oleh GHad mengukur banyak hal lain (seperti refleksi, objek instantiating, dll.) Selain mendapatkan panjangnya. Jika kita mencoba untuk menyingkirkan hal-hal ini maka untuk satu panggilan saya dapatkan waktu berikut dalam mikrodetik:

   jumlah file ___ 19.0, per Iterasi ___ 19.0
    jumlah raf ___ 16.0, per Iterasi ___ 16.0
jumlah saluran__273.0, per Iteration__273.0

Untuk 100 berjalan dan 10.000 iterasi saya dapatkan:

   file sum__1767629.0, per Iteration__1.7676290000000001
    raf sum ___ 881284.0, per Iteration__0.8812840000000001
jumlah saluran ___ 414286.0, per Iteration__0.414286

Saya memang menjalankan kode yang dimodifikasi berikut memberikan sebagai argumen nama file 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));
    }
  }
}
basilikode
sumber
3
sebenarnya, sementara Anda benar mengatakan itu mengukur aspek-aspek lain, saya harus lebih jelas dalam pertanyaan saya. Saya mencari untuk mendapatkan ukuran file dari banyak file, dan saya ingin cara tercepat mungkin. jadi saya benar-benar perlu mempertimbangkan pembuatan objek dan overhead, karena itu adalah skenario nyata
joshjdevl
3
Sekitar 90% dari waktu dihabiskan untuk hal getResource itu. Saya ragu Anda perlu menggunakan refleksi untuk mendapatkan nama file yang berisi beberapa bytecode Java.
20

Semua kasus uji dalam posting ini cacat karena mereka mengakses file yang sama untuk setiap metode yang diuji. Jadi, cache caching menghasilkan tes 2 dan 3. Untuk membuktikan pendapat saya, saya mengambil test case yang disediakan oleh GHAD dan mengubah urutan enumerasi dan berikut hasilnya.

Melihat hasil, saya pikir File.length () adalah pemenangnya.

Urutan tes adalah urutan output. Anda bahkan dapat melihat waktu yang dibutuhkan pada mesin saya bervariasi antara eksekusi tetapi File.Length () ketika tidak pertama, dan menimbulkan akses disk pertama dimenangkan.

---
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
StuartH
sumber
9

Ketika saya memodifikasi kode Anda untuk menggunakan file yang diakses oleh jalur absolut alih-alih sumber daya, saya mendapatkan hasil yang berbeda (untuk 1 run, 1 iterasi, dan file 100.000 byte - kali untuk file 10 byte identik dengan 100.000 byte )

PANJANG jumlah: 33, per Iterasi: 33.0

Jumlah CHANNEL: 3626, per Iterasi: 3626.0

Jumlah URL: 294, per Iterasi: 294.0

tgdavies
sumber
9

Menanggapi tolok ukur rgrig, waktu yang dibutuhkan untuk membuka / menutup instance FileChannel & RandomAccessFile juga perlu diperhitungkan, karena kelas-kelas ini akan membuka aliran untuk membaca file.

Setelah memodifikasi patokan, saya mendapat hasil ini untuk 1 iterasi pada file 85MB:

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

Untuk 10.000 iterasi pada file yang sama:

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

Jika yang Anda butuhkan adalah ukuran file, file.length () adalah cara tercepat untuk melakukannya. Jika Anda berencana untuk menggunakan file untuk tujuan lain seperti membaca / menulis, maka RAF tampaknya menjadi taruhan yang lebih baik. Hanya saja jangan lupa untuk menutup koneksi file :-)

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";
        } 
    }
}
Karthikeyan
sumber
8

Saya mengalami masalah yang sama. Saya perlu mendapatkan ukuran file dan tanggal modifikasi dari 90.000 file di jaringan berbagi. Menggunakan Java, dan menjadi seminimal mungkin, itu akan memakan waktu yang sangat lama. (Saya perlu mendapatkan URL dari file, dan jalur objek juga. Jadi agak bervariasi, tetapi lebih dari satu jam.) Saya kemudian menggunakan executable Win32 asli, dan melakukan tugas yang sama, hanya membuang file jalan, dimodifikasi, dan ukuran ke konsol, dan dieksekusi itu dari Jawa. Kecepatannya luar biasa. Proses asli, dan penanganan string saya untuk membaca data dapat memproses lebih dari 1000 item per detik.

Jadi, meskipun orang-orang di bawah peringkat komentar di atas, ini adalah solusi yang valid, dan memang memecahkan masalah saya. Dalam kasus saya, saya tahu folder yang saya butuhkan ukuran sebelumnya, dan saya bisa meneruskannya di baris perintah ke aplikasi win32 saya. Saya beralih dari jam ke proses direktori ke menit.

Masalahnya juga tampaknya khusus untuk Windows. OS X tidak memiliki masalah yang sama dan dapat mengakses informasi file jaringan secepat OS dapat melakukannya.

Penanganan File Java di Windows sangat buruk. Akses disk lokal untuk file baik-baik saja. Itu hanya jaringan berbagi yang menyebabkan kinerja yang mengerikan. Windows dapat memperoleh info tentang jaringan berbagi dan menghitung ukuran total dalam waktu kurang dari satu menit juga.

--Ben

Ben Spink
sumber
3

Jika Anda ingin ukuran file beberapa file dalam direktori, gunakan Files.walkFileTree. Anda dapat memperoleh ukuran dariBasicFileAttributes yang akan Anda terima.

Ini jauh lebih cepat daripada memanggil .length()hasil File.listFiles()atau menggunakan Files.size()hasil Files.newDirectoryStream(). Dalam kasus pengujian saya sekitar 100 kali lebih cepat.

Scg
sumber
FYI, Files.walkFileTreetersedia di Android 26+.
Joshua Pinter
2

Sebenarnya, saya pikir "ls" mungkin lebih cepat. Pasti ada beberapa masalah di Jawa yang berhubungan dengan mendapatkan info File. Sayangnya tidak ada metode rekursif aman yang setara untuk Windows. (DIR / S cmd.exe bisa membingungkan dan menghasilkan kesalahan dalam loop tak terbatas)

Di XP, mengakses server di LAN, saya butuh 5 detik di Windows untuk mendapatkan jumlah file dalam folder (33.000), dan ukuran total.

Ketika saya mengulanginya secara berulang di Jawa, saya membutuhkan waktu lebih dari 5 menit. Saya mulai mengukur waktu yang diperlukan untuk melakukan file.length (), file.lastModified (), dan file.toURI () dan apa yang saya temukan adalah bahwa 99% dari waktu saya diambil oleh 3 panggilan itu. 3 panggilan yang sebenarnya harus saya lakukan ...

Perbedaan untuk 1000 file adalah 15ms lokal versus 1800ms di server. Pemindaian jalur server di Jawa sangat lambat. Jika OS asli bisa cepat memindai folder yang sama, mengapa tidak bisa Java?

Sebagai tes yang lebih lengkap, saya menggunakan WineMerge di XP untuk membandingkan tanggal yang dimodifikasi, dan ukuran file di server versus file secara lokal. Ini mengulangi seluruh pohon direktori dari 33.000 file di setiap folder. Total waktu, 7 detik. java: lebih dari 5 menit.

Jadi pernyataan dan pertanyaan asli dari OP itu benar, dan valid. Ini kurang terlihat ketika berhadapan dengan sistem file lokal. Melakukan perbandingan folder secara lokal dengan 33.000 item membutuhkan waktu 3 detik di WinMerge, dan memakan waktu 32 detik secara lokal di Java. Jadi sekali lagi, java versus asli adalah perlambatan 10x dalam tes dasar ini.

Java 1.6.0_22 (terbaru), Gigabit LAN, dan koneksi jaringan, ping kurang dari 1ms (keduanya dalam switch yang sama)

Java lambat.

Ben Spink
sumber
2
Ini juga tampaknya spesifik OS. Melakukan aplikasi java yang sama setelah folder yang sama dari OS X menggunakan samba butuh 26 detik untuk mendaftar seluruh 33.000 item, ukuran, dan tanggal. Jadi jaringan Java lambat di Windows? (OS X juga java 1.6.0_22.)
Ben Spink
2

Dari tolok ukur GHad, ada beberapa masalah yang disebutkan orang:

1> Seperti yang disebutkan BalusC: stream.available () mengalir dalam kasus ini.

Karena tersedia () mengembalikan estimasi jumlah byte yang dapat dibaca (atau dilompati) dari aliran input ini tanpa menghalangi dengan permohonan metode berikutnya untuk aliran input ini.

Jadi 1 untuk menghapus URL pendekatan ini.

2> Seperti yang disebutkan StuartH - urutan tes juga membuat perbedaan cache, jadi keluarkan dengan menjalankan tes secara terpisah.


Sekarang mulailah tes:

Ketika CHANNEL satu dijalankan sendiri:

CHANNEL sum: 59691, per Iteration: 238.764

Ketika PANJANG satu berjalan sendiri:

LENGTH sum: 48268, per Iteration: 193.072

Jadi sepertinya PANJANG adalah pemenangnya di sini:

@Override
public long getResult() throws Exception {
    File me = new File(FileSizeBench.class.getResource(
            "FileSizeBench.class").getFile());
    return me.length();
}
Gob00st
sumber