java wydajnie pobiera rozmiar pliku

166

Podczas wyszukiwania w Google widzę, że używanie java.io.File#length()może być powolne. FileChannelma również size()dostępną metodę.

Czy w Javie istnieje skuteczny sposób na uzyskanie rozmiaru pliku?

joshjdevl
źródło
7
czy możesz podać linki, które mówią, że File.length () „może działać wolno”?
mat b
1
przepraszam, tutaj jest łącze javaperformancetuning.com/tips/rawtips.shtml, wyszukaj hasło „Informacje o pliku, takie jak File.length (), wymagają wywołania systemowego i mogą być wolne”. to naprawdę mylące stwierdzenie, wydaje się prawie zakładane, że będzie to wywołanie systemowe.
joshjdevl
25
Uzyskanie długości pliku będzie wymagało wywołania systemowego bez względu na to, jak to zrobisz. Może być powolny, jeśli jest połączony z siecią lub innym bardzo wolnym systemem plików. Nie ma szybszego sposobu na uzyskanie tego niż File.length (), a definicja „wolno” oznacza tutaj po prostu nie wywołuj go niepotrzebnie.
jsight
Myślę, że to właśnie GHad próbował przetestować poniżej. Moje wyniki to (w systemie Ubuntu 8.04): tylko jeden adres URL dostępu jest najszybszy. 5 przebiegów, 50 iteracji CHANNEL jest jeszcze najszybszy? :) jednak dla moich celów zrobię tylko jeden dostęp. chociaż to dziwne? że otrzymaliśmy inne wyniki
joshjdevl
1
Ta operacja może być bardzo powolna, jeśli informacje znajdują się na dysku, a nie w pamięci podręcznej. (jak 1000x wolniej), jednak niewiele można z tym zrobić poza upewnieniem się, że potrzebne informacje są zawsze w pamięci podręcznej (takie jak wstępne ładowanie i posiadanie wystarczającej ilości pamięci, aby pozostała w pamięci)
Peter Lawrey

Odpowiedzi:

102

Cóż, próbowałem to zmierzyć za pomocą poniższego kodu:

Dla uruchomień = 1 i iteracji = 1 metoda adresu URL jest najszybsza w większości przypadków, po której następuje kanał. Uruchamiam to z pewną przerwą około 10 razy. Tak więc przy jednorazowym dostępie użycie adresu URL to najszybszy sposób, jaki mogę sobie wyobrazić:

LENGTH sum: 10626, per Iteration: 10626.0

CHANNEL sum: 5535, per Iteration: 5535.0

URL sum: 660, per Iteration: 660.0

Dla przebiegów = 5 i iteracji = 50 obraz rysuje się inaczej.

LENGTH sum: 39496, per Iteration: 157.984

CHANNEL sum: 74261, per Iteration: 297.044

URL sum: 95534, per Iteration: 382.136

Plik musi buforować wywołania systemu plików, podczas gdy kanały i URL mają trochę narzutu.

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

}
GHad
źródło
1
Wygląda na to, że droga URL jest najlepsza dla pojedynczego dostępu, niezależnie od tego, czy jest to XP, czy Linux. Greetz GHad
GHad,
73
stream.available()nie zwraca długości pliku. Zwraca liczbę bajtów, które są dostępne do odczytu bez blokowania innych strumieni. Niekoniecznie jest to ta sama liczba bajtów, co długość pliku. Aby uzyskać rzeczywistą długość strumienia, naprawdę musisz go przeczytać (i w międzyczasie policzyć odczytane bajty).
BalusC
11
Ten punkt odniesienia jest, a raczej jego interpretacja jest nieprawidłowa. W przypadku małej liczby iteracji późniejsze testy wykorzystują buforowanie plików systemu operacyjnego. W teście wyższych iteracji ranking jest poprawny, ale nie dlatego, że File.length () buforuje coś, ale po prostu dlatego, że pozostałe 2 opcje są oparte na tej samej metodzie, ale wykonują dodatkową pracę, która je spowalnia.
x4u
2
@Paolo, buforowanie i optymalizacja dostępu do systemu plików jest jednym z głównych obowiązków systemu operacyjnego. faqs.org/docs/linux_admin/buffer-cache.html Aby uzyskać dobre wyniki testów porównawczych, pamięć podręczną należy wyczyścić przed każdym uruchomieniem.
z0r
3
Poza tym, co mówi javadoc dla InputStream.available (), fakt, że metoda available () zwraca wartość int, powinien być czerwoną flagą w stosunku do podejścia URL. Wypróbuj to z plikiem 3 GB, a okaże się, że nie jest to prawidłowy sposób określenia długości pliku.
Scrubbie
32

Wzorzec podany przez GHad mierzy wiele innych rzeczy (takich jak odbicie, tworzenie instancji obiektów itp.) Oprócz określenia długości. Jeśli spróbujemy się tego pozbyć, to dla jednego połączenia otrzymam następujące czasy w mikrosekundach:

   suma pliku ___ 19,0, na Iterację ___ 19,0
    suma raf ___ 16,0, na Iterację ___ 16,0
suma kanałów__273.0, na Iterację__273.0

Za 100 przebiegów i 10000 iteracji otrzymuję:

   suma pliku__1767629.0, na iterację__1.7676290000000001
    suma raf ___ 881284,0, na iterację__0,8812840000000001
suma kanałów ___ 414286,0, na iterację__0,414286

Uruchomiłem następujący zmodyfikowany kod, podając jako argument nazwę pliku 100 MB.

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
źródło
3
w rzeczywistości, chociaż ma pan rację, mówiąc, że mierzy on inne aspekty, to moje pytanie powinno być jaśniejsze. Chcę uzyskać rozmiar pliku wielu plików i chcę jak najszybciej. więc naprawdę muszę wziąć pod uwagę tworzenie obiektów i koszty ogólne, ponieważ to prawdziwy scenariusz
joshjdevl
3
Około 90% czasu spędza się na getResource. Wątpię, abyś musiał użyć odbicia, aby uzyskać nazwę pliku, który zawiera kod bajtowy Javy.
20

Wszystkie przypadki testowe w tym poście są wadliwe, ponieważ mają dostęp do tego samego pliku dla każdej testowanej metody. Więc buforowanie dysku zaczyna działać, w którym testy 2 i 3 odnoszą korzyści. Aby udowodnić swoją rację, wziąłem testowy przypadek dostarczony przez GHAD i zmieniłem kolejność wyliczania i poniżej są wyniki.

Patrząc na wynik, myślę, że File.length () jest naprawdę zwycięzcą.

Kolejność testu to kolejność wyników. Możesz nawet zobaczyć, jak czas potrzebny na moim komputerze różnił się między wykonaniami, ale File.Length (), gdy nie był pierwszy, i wygrał pierwszy dostęp do dysku.

---
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
źródło
9

Kiedy zmodyfikuję twój kod, aby używał pliku dostępnego przez ścieżkę bezwzględną zamiast zasobu, otrzymuję inny wynik (dla 1 uruchomienia, 1 iteracji i pliku 100 000 bajtów - czasy dla pliku 10-bajtowego są identyczne jak 100 000 bajtów )

Suma DŁUGOŚĆ: 33, na Iterację: 33,0

Suma KANAŁÓW: 3626, na Iterację: 3626,0

Suma adresów URL: 294, na iterację: 294,0

tgdavies
źródło
9

W odpowiedzi na test porównawczy rgriga, należy również wziąć pod uwagę czas potrzebny do otwarcia / zamknięcia instancji FileChannel i RandomAccessFile, ponieważ te klasy będą otwierać strumień do odczytu pliku.

Po zmodyfikowaniu testu porównawczego otrzymałem następujące wyniki dla 1 iteracji na pliku 85 MB:

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

Dla 10000 iteracji tego samego pliku:

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

Jeśli potrzebujesz tylko rozmiaru pliku, najszybszym sposobem jest file.length (). Jeśli planujesz użyć pliku do innych celów, takich jak czytanie / pisanie, wówczas RAF wydaje się być lepszym rozwiązaniem. Tylko nie zapomnij zamknąć połączenia pliku :-)

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
źródło
8

Napotkałem ten sam problem. Musiałem uzyskać rozmiar pliku i datę modyfikacji 90 000 plików w udziale sieciowym. Używanie Java i bycie tak minimalistycznym, jak to tylko możliwe, zajęłoby bardzo dużo czasu. (Musiałem uzyskać adres URL z pliku, a także ścieżkę do obiektu. Więc było to nieco zróżnicowane, ale ponad godzinę). Następnie użyłem natywnego pliku wykonywalnego Win32 i wykonałem to samo zadanie, po prostu zrzucając plik ścieżka, zmodyfikowana i rozmiar do konsoli i wykonane z Java. Prędkość była niesamowita. Proces natywny i moja obsługa ciągów w celu odczytania danych mogą przetwarzać ponad 1000 elementów na sekundę.

Więc chociaż ludzie niżej ocenili powyższy komentarz, jest to poprawne rozwiązanie i rozwiązało mój problem. W moim przypadku z wyprzedzeniem znałem foldery, których potrzebowałem, i mogłem przekazać to w wierszu poleceń do mojej aplikacji win32. Przetwarzanie katalogu zajęło mi kilka minut.

Wydawało się, że problem dotyczy również systemu Windows. OS X nie miał tego samego problemu i mógł uzyskać dostęp do informacji o plikach sieciowych tak szybko, jak mógł to zrobić system operacyjny.

Obsługa plików Java w systemie Windows jest okropna. Dostęp do plików na dysku lokalnym jest jednak w porządku. To właśnie udziały sieciowe spowodowały straszną wydajność. Windows może uzyskać informacje o udziale sieciowym i obliczyć całkowity rozmiar w mniej niż minutę.

- Ben

Ben Spink
źródło
3

Jeśli chcesz, aby rozmiar pliku obejmował wiele plików w katalogu, użyj Files.walkFileTree. Możesz uzyskać rozmiar z tego BasicFileAttributes, który otrzymasz.

Jest to znacznie szybsze niż wywołanie .length()wyniku File.listFiles()lub użycie Files.size()wyniku Files.newDirectoryStream(). W moich przypadkach testowych było około 100 razy szybciej.

Scg
źródło
FYI, Files.walkFileTreejest dostępny na Androida 26+.
Joshua Pinter
2

Właściwie myślę, że „ls” może być szybsze. Zdecydowanie w Javie występują problemy z pobieraniem informacji o pliku. Niestety nie ma równoważnej bezpiecznej metody rekursywnego ls dla Windows. (DIR / S cmd.exe może się mylić i generować błędy w nieskończonych pętlach)

W XP, uzyskując dostęp do serwera w sieci LAN, w systemie Windows potrzebuję 5 sekund, aby uzyskać liczbę plików w folderze (33 000) i całkowity rozmiar.

Kiedy powtarzam to w Javie, zajmie mi to ponad 5 minut. Zacząłem mierzyć czas potrzebny na wykonanie file.length (), file.lastModified () i file.toURI () i odkryłem, że 99% mojego czasu zajmuje te 3 wywołania. 3 rozmowy, które faktycznie muszę wykonać ...

Różnica dla 1000 plików to 15 ms lokalnie w porównaniu z 1800 ms na serwerze. Skanowanie ścieżek serwera w Javie jest absurdalnie wolne. Jeśli natywny system operacyjny może szybko skanować ten sam folder, dlaczego nie może Java?

Jako pełniejszy test użyłem WineMerge na XP, aby porównać datę modyfikacji i rozmiar plików na serwerze z plikami lokalnie. To było iteracyjne w całym drzewie katalogów 33 000 plików w każdym folderze. Całkowity czas 7 sekund. java: ponad 5 minut.

Zatem oryginalne oświadczenie i pytanie z PO są prawdziwe i ważne. Jest mniej zauważalny w przypadku lokalnego systemu plików. Wykonanie lokalnego porównania folderu zawierającego 33 000 elementów zajmuje 3 sekundy w WinMerge i 32 sekundy lokalnie w Javie. Więc znowu, java versus natywna to 10-krotne spowolnienie w tych podstawowych testach.

Java 1.6.0_22 (najnowsza), Gigabit LAN i połączenia sieciowe, ping jest mniejszy niż 1 ms (oba w tym samym przełączniku)

Java jest powolna.

Ben Spink
źródło
2
Wydaje się, że dotyczy to również konkretnego systemu operacyjnego. Wykonując tę ​​samą aplikację java, przechodząc do tego samego folderu z systemu OS X za pomocą samby, wyświetlenie wszystkich 33 000 elementów, rozmiarów i dat zajęło 26 sekund. Czyli Java sieciowa działa po prostu wolno w systemie Windows? (OS X to również java 1.6.0_22).
Ben Spink,
2

Z testu porównawczego GHad wynika kilka problemów, o których wspomnieli ludzie:

1> Jak wspomniano BalusC: stream.available () jest przepływana w tym przypadku.

Ponieważ available () zwraca szacunkową liczbę bajtów, które można odczytać (lub pominąć) z tego strumienia wejściowego bez blokowania przez następne wywołanie metody dla tego strumienia wejściowego.

Więc po pierwsze, aby usunąć adres URL to podejście.

2> Jak wspomniał StuartH - kolejność uruchomienia testu również powoduje różnicę w pamięci podręcznej, więc usuń to, uruchamiając test osobno.


Teraz rozpocznij test:

Kiedy CHANNEL one działa sam:

CHANNEL sum: 59691, per Iteration: 238.764

Gdy DŁUGOŚĆ jeden biegnie sam:

LENGTH sum: 48268, per Iteration: 193.072

Wygląda więc na to, że LENGTH jest tutaj zwycięzcą:

@Override
public long getResult() throws Exception {
    File me = new File(FileSizeBench.class.getResource(
            "FileSizeBench.class").getFile());
    return me.length();
}
Gob00st
źródło