java dosya boyutunu verimli bir şekilde alır


166

Google'ı kullanırken java.io.File#length(), kullanımın yavaş olabileceğini görüyorum . FileChannelsahipsize()kullanılabilir yöntemi vardır.

Java'da dosya boyutunu almanın etkili bir yolu var mı?


7
File.length () "yavaş olabilir" diyerek link verebilir misiniz?
matt b

1
üzgünüz, javaperformancetuning.com/tips/rawtips.shtml bağlantı "File.length () gibi dosya bilgileri bir sistem çağrısı gerektirir ve yavaş olabilir." bu gerçekten kafa karıştırıcı bir ifade, neredeyse bir sistem çağrısı olacağı varsayılıyor.
joshjdevl

25
Dosya uzunluğunu almak, nasıl yaparsanız yapın bir sistem çağrısı gerektirir. Bir ağ üzerinden veya başka bir çok yavaş dosya sistemi üzerinden yavaş olabilir. Bunu almanın File.length () yönteminden daha hızlı bir yolu yoktur ve "yavaş" tanımı burada gereksiz yere çağırma anlamına gelmez.
jsight

Sanırım GHad aşağıda test etmeye çalışıyordu. Sonuçlarım (Ubuntu 8.04'te): sadece bir erişim URL'si en hızlı. 5 koşu, 50 yineleme KANAL henüz en hızlı kafa karıştırıcı mı? :) benim amaçlarım için olsa da, ben sadece bir erişim yapacağım. garip olsa da? farklı sonuçlar aldık
joshjdevl

1
Bilgiler önbellek yerine disk üzerindeyse bu işlem çok yavaş olabilir. (1000x daha yavaş gibi), ancak ihtiyacınız olan bilgilerin her zaman önbellekte olmasını sağlamak dışında bu konuda yapabileceğiniz çok az şey vardır (
önyükleme

Yanıtlar:


102

Eh, aşağıdaki kod ile ölçmek için çalıştı:

Run = 1 ve iterations = 1 için URL yöntemi çoğu zaman en hızlıdır ve bunu kanal takip eder. Bunu yaklaşık 10 kez taze bir duraklama ile çalıştırıyorum. Bir kerelik erişim için, URL'yi kullanmak aklıma gelen en hızlı yoldur:

LENGTH sum: 10626, per Iteration: 10626.0

CHANNEL sum: 5535, per Iteration: 5535.0

URL sum: 660, per Iteration: 660.0

Koşu = 5 ve yineleme = 50 için resim farklı çizer.

LENGTH sum: 39496, per Iteration: 157.984

CHANNEL sum: 74261, per Iteration: 297.044

URL sum: 95534, per Iteration: 382.136

Kanallar ve URL'de bazı ek yükler varken, dosya dosya sistemine yapılan çağrıları önbelleğe almalıdır.

Kod:

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
URL yolu, XP veya linux gibi tek erişim için gidilebilecek en iyi yol gibi görünüyor. Greetz GHad
GHad

73
stream.available()dosya uzunluğunu döndürmez. Diğer akışları engellemeden okunabilecek bayt miktarını döndürür. Dosya uzunluğu ile aynı miktarda bayt olması gerekmez. Bir akıştan gerçek uzunluğu elde etmek için gerçekten okumalısınız (ve bu arada okuma baytlarını saymanız gerekir).
BalusC

11
Bu kriter ya da daha doğrusu yorumu doğru değildir. Düşük yineleme sayısında, sonraki testler işletim sisteminin dosya önbelleklemesinden yararlanır. Daha yüksek iterasyon testinde sıralama doğrudur, ancak File.length () bir şey önbelleğe aldığı için değil, diğer 2 seçenek aynı yönteme dayalı olduğu için, ancak onları yavaşlatan ekstra iş yaptığından dolayı.
x4u

2
@Paolo, dosya sistemi erişimini önbelleğe almak ve optimize etmek bir işletim sisteminin en önemli sorumluluklarından biridir. faqs.org/docs/linux_admin/buffer-cache.html İyi karşılaştırma sonuçları elde etmek için her çalıştırmadan önce önbelleğin temizlenmesi gerekir.
z0r

3
InputStream.available () için javadoc'un söylediklerinin ötesinde, available () yönteminin bir int döndürmesi, URL yaklaşımına karşı kırmızı bir bayrak olmalıdır. 3GB'lık bir dosyayla deneyin ve dosya uzunluğunu belirlemenin geçerli bir yolu olmadığı açıktır.
Scrubbie

32

GHad tarafından verilen kriter, uzunluğu elde etmenin yanı sıra birçok başka şeyi (yansıma, somut nesneler vb.) Ölçer. Bu şeylerden kurtulmaya çalışırsak, bir çağrı için mikrosaniye cinsinden aşağıdaki süreleri elde ederim:

   Dosya toplamı ___ 19.0, Yineleme başına ___ 19.0
    raf toplamı ___ 16.0, Yineleme başına ___ 16.0
Kanal toplamı 273,0, Yineleme başına

100 koşu ve 10000 yineleme için:

   yineleme başına sum__1767629.0 dosyası, 1.7676290000000001
    raf toplamı ___ 881284.0, Yineleme başına0.8812840000000001
kanal toplamı ___ 414286.0, Yineleme başına ___ 0.414286

Aşağıdaki değiştirilen kodu 100MB dosyasının adını argüman olarak vererek çalıştırdım.

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
aslında, diğer yönleri ölçtüğünü söylerken haklı olsanız da, sorumda daha açık olmalıyım. Birden fazla dosyanın dosya boyutunu almak için arıyorum ve mümkün olan en hızlı yolu istiyorum. Bu yüzden gerçekten gerçek bir senaryo olduğundan nesne oluşturma ve ek yükü hesaba katmak gerekir
joshjdevl

3
Zamanın yaklaşık% 90'ı bu getResource işinde geçirilir. Bazı Java bayt kodu içeren bir dosyanın adını almak için yansıma kullanmanız gerektiğinden şüpheliyim.

20

Bu gönderideki tüm test senaryoları, test edilen her yöntem için aynı dosyaya eriştiklerinden kusurludur. Dolayısıyla, test 2 ve 3'ün faydalandığı disk önbellekleme vuruşları. Benim açımdan kanıtlamak için GHAD tarafından sağlanan test davasını aldım ve numaralandırma sırasını değiştirdim ve aşağıda sonuçlar var.

Sonuca baktığımda File.length () gerçekten kazanan olduğunu düşünüyorum.

Test sırası çıktı sırasıdır. Makinemde geçen sürenin yürütmeler arasında değiştiğini, ancak ilk olmadığında File.Length () olduğunu ve ilk disk erişiminin kazanıldığını görebilirsiniz.

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

Kodunuzu kaynak yerine mutlak bir yolla erişilen bir dosyayı kullanacak şekilde değiştirdiğimde, farklı bir sonuç alıyorum (1 çalıştırma, 1 yineleme ve 100.000 bayt dosya için - 10 baytlık bir dosya için 100.000 bayt ile aynıdır) )

UZUNLUK toplamı: 33, Yineleme başına: 33.0

KANAL toplamı: 3626, Yineleme başına: 3626.0

URL toplamı: 294, yineleme başına: 294.0


9

Rgrig'in karşılaştırmalı değerlendirmesine yanıt olarak, FileChannel & RandomAccessFile örneklerinin açılması / kapatılması için geçen sürenin de dikkate alınması gerekir, çünkü bu sınıflar dosyayı okumak için bir akış açacaktır.

Karşılaştırmayı değiştirdikten sonra, 85 MB'lık bir dosyada 1 yineleme için şu sonuçları aldım:

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

Aynı dosyada 10000 yineleme için:

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

Tek ihtiyacınız olan dosya boyutu ise, file.length () bunu yapmanın en hızlı yoludur. Dosyayı okuma / yazma gibi başka amaçlarla kullanmayı planlıyorsanız, RAF daha iyi bir bahis gibi görünüyor. Sadece dosya bağlantısını kapatmayı unutma :-)

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

Aynı sorunla karşılaştım. Bir ağ paylaşımında dosya boyutunu ve 90.000 dosyanın değiştirilme tarihini almam gerekiyordu. Java kullanmak ve olabildiğince minimalist olmak çok uzun zaman alacaktır. (Ben de dosyadan URL ve nesnenin yolunu almak gerekiyordu. Bu yüzden biraz değişti, ama bir saatten fazla.) Sonra yerel bir Win32 çalıştırılabilir ve aynı dosyayı yaptım, sadece dosyayı damping yol, değiştirilmiş ve konsolun boyutuna getirilmiş ve Java'dan çalıştırılmıştır. Hız şaşırtıcıydı. Yerel işlem ve verileri okumak için dize işleme, saniyede 1000'den fazla öğeyi işleyebilir.

Bu yüzden insanlar yukarıdaki yorumu sıralamasına rağmen, bu geçerli bir çözüm ve sorunumu çözdü. Benim durumumda önceden belirlenmiş boyutlara ihtiyacım olan klasörleri biliyordum ve bunu komut satırında win32 uygulamama aktarabiliyordum. Bir dizini işlemek için saatlerden dakikalara gittim.

Sorun Windows'a özgü gibi görünüyordu. OS X ile aynı sorun yoktu ve işletim sisteminin yapabildiği kadar hızlı ağ dosya bilgilerine erişebiliyordu.

Windows'da Java Dosya kullanımı korkunç. Dosyalar için yerel disk erişimi gayet iyi. Sadece korkunç performansa neden olan ağ paylaşımlarıydı. Windows ağ paylaşımı hakkında bilgi alabilir ve bir dakikadan kısa sürede toplam boyutu hesaplayabilir.

--Ben


3

Bir dizindeki birden çok dosyanın dosya boyutunu istiyorsanız, tuşunu kullanın Files.walkFileTree. Size, BasicFileAttributesalacağınız boyuttan ulaşabilirsiniz .

Bu çok daha hızlı sonra aradığını .length()sonucuna File.listFiles()kullanılarak veya Files.size()sonucuna Files.newDirectoryStream(). Test vakalarımda 100 kat daha hızlıydı.


FYI, Files.walkFileTreeAndroid 26 ve sonraki sürümlerinde kullanılabilir.
Joshua Pinter

2

Aslında, "ls" daha hızlı olabilir düşünüyorum. Java'da kesinlikle Dosya bilgisi almakla ilgili bazı sorunlar vardır. Ne yazık ki Windows için özyinelemeli ls eşdeğer güvenli bir yöntemi yoktur. (cmd.exe'nin DIR / S'si karışabilir ve sonsuz döngülerde hatalar oluşturabilir)

XP'de, LAN üzerindeki bir sunucuya erişmek, Windows'ta bir klasördeki dosya sayısını (33.000) ve toplam boyutu almam 5 saniye sürer.

Java ile yinelemeli olarak yinelediğimde, 5 dakikadan fazla sürüyor. File.length (), file.lastModified () ve file.toURI () için gereken süreyi ölçmeye başladım ve bulduğum şey, zamanımın% 99'unun bu 3 çağrı tarafından alındığı. Aslında yapmam gereken 3 çağrı ...

1000 dosya arasındaki fark sunucuda 1800 ms'ye karşılık 15 ms yereldir. Java'da sunucu yolu taraması gülünç derecede yavaştır. Yerel işletim sistemi aynı klasörü taramakta hızlıysa neden Java?

Daha eksiksiz bir test olarak, sunucudaki dosyaların değiştirilme tarihini ve boyutunu yerel dosyalarla karşılaştırmak için XP'de WineMerge kullandım. Bu, her klasördeki 33.000 dosyanın dizin ağacının tamamını yineliyordu. Toplam süre, 7 saniye. java: 5 dakikadan fazla.

Yani OP'nin orijinal ifadesi ve sorusu doğru ve geçerlidir. Yerel bir dosya sistemi ile uğraşırken daha az fark edilir. Klasörün 33.000 öğeyle yerel olarak karşılaştırılması WinMerge'de 3 saniye, Java'da yerel olarak 32 saniye sürer. Yine, java'ya karşı bu ilkel testlerde 10 kat yavaşlamadır.

Java 1.6.0_22 (en yeni), Gigabit LAN ve ağ bağlantıları, ping 1ms'den az (her ikisi de aynı anahtarda)

Java yavaş.


2
Bu aynı zamanda işletim sistemine özel görünmektedir. Aynı java uygulamasını samba kullanarak OS X'ten aynı klasörden sonra yapmak 33.000 öğenin tamamını, boyutlarını ve tarihlerini listelemek 26 saniye sürdü. Peki Windows'da ağ Java yavaş mı? (OS X de java 1.6.0_22 idi.)
Ben Spink

2

GHad'ın kıstasından, insanların bahsettiği birkaç sorun var:

1> BalusC'un belirttiği gibi: bu durumda stream.available () akar.

Available () yöntemi, bu giriş akışı için bir yöntemin bir sonraki çağrılması engellenmeden bu giriş akışından okunabilen (veya atlanabilen) bayt sayısının bir tahminini döndürür .

Yani 1 URL kaldırmak için bu yaklaşım.

2> StuartH'ın belirttiği gibi - test çalışmasının sırası da önbellek farkını yapar, bu yüzden testi ayrı olarak çalıştırarak çıkarın.


Şimdi testi başlat:

CHANNEL tek başına koştuğunda:

CHANNEL sum: 59691, per Iteration: 238.764

UZUNLUK tek başına koştuğunda:

LENGTH sum: 48268, per Iteration: 193.072

Görünüşe göre UZUNLUK burada kazanan:

@Override
public long getResult() throws Exception {
    File me = new File(FileSizeBench.class.getResource(
            "FileSizeBench.class").getFile());
    return me.length();
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.