Podczas wyszukiwania w Google widzę, że używanie java.io.File#length()
może być powolne.
FileChannel
ma również size()
dostępną metodę.
Czy w Javie istnieje skuteczny sposób na uzyskanie rozmiaru pliku?
Podczas wyszukiwania w Google widzę, że używanie java.io.File#length()
może być powolne.
FileChannel
ma również size()
dostępną metodę.
Czy w Javie istnieje skuteczny sposób na uzyskanie rozmiaru pliku?
Odpowiedzi:
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;
}
}
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).
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));
}
}
}
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
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
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";
}
}
}
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
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.
Files.walkFileTree
jest dostępny na Androida 26+.
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.
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();
}