Під час googling я бачу, що використання java.io.File#length()
може бути повільним.
FileChannel
є також size()
доступний метод.
Чи є ефективний спосіб у Java отримати розмір файлу?
Під час googling я бачу, що використання java.io.File#length()
може бути повільним.
FileChannel
є також size()
доступний метод.
Чи є ефективний спосіб у Java отримати розмір файлу?
Відповіді:
Ну, я спробував виміряти це за допомогою коду нижче:
Для запусків = 1 та ітерацій = 1, метод URL найшвидший, а найчастіше - канал. Я запускаю це з деякою паузою, приблизно 10 разів. Тож для разового доступу користування 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;
}
}
stream.available()
не повертає довжину файлу. Він повертає кількість байтів, доступних для читання, не блокуючи інших потоків. Не обов’язково така ж кількість байтів, як довжина файлу. Щоб отримати реальну довжину від потоку, вам дійсно потрібно його прочитати (а тим часом порахувати прочитані байти).
Орієнтир, який дає GHad, вимірює безліч інших речей (наприклад, відображення, створення об'єктів тощо), окрім отримання довжини. Якщо ми спробуємо позбутися цих речей, то за один дзвінок я отримую наступні рази в мікросекундах:
сума файлу ___ 19,0, за ітерацію ___ 19,0 raf сума ___ 16,0, за ітерацію ___ 16,0 сума каналу__273.0, за ітерацію__273.0
За 100 пробіжок і 10000 ітерацій я отримую:
сума файлу__1767629.0, за ітерацію__1.7676290000000001 raf сума ___ 881284,0, за ітерацію__0,8812840000000001 сума каналу ___ 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));
}
}
}
Усі тестові випадки в цій публікації є помилковими, оскільки вони отримують доступ до одного файлу для кожного тестованого методу. Отже, кешування диска починає керуватися тестами 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
Коли я змінюю ваш код, щоб використовувати файл, доступ до якого має абсолютний шлях замість ресурсу, я отримую інший результат (за 1 пробіг, 1 ітерацію та файл 100 000 байт - рази для 10 байтного файлу ідентичні 100 000 байтам )
Сума довжини: 33, за повторення: 33,0
Сума КАНАЛУ: 3626, за повторення: 3626,0
Сума URL-адрес: 294, за повторення: 294.0
У відповідь на орієнтир rgrig, також потрібно враховувати час для відкриття / закриття екземплярів FileChannel & RandomAccessFile, оскільки ці класи відкриють потік для читання файлу.
Після зміни еталону я отримав ці результати за 1 ітерацію у файлі 85 МБ:
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)
Якщо все, що вам потрібно, це розмір файлу, файл.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";
}
}
}
Я зіткнувся з цим самим питанням. Мені потрібно було отримати розмір файлу та модифіковану дату 90 000 файлів на загальній мережі. Використовуючи Java та будучи максимально мінімалістичним, це зайняло б дуже багато часу. (Мені потрібно було отримати URL-адресу з файлу, а також шлях об'єкта. Отже, його дещо варіювалося, але більше години.) Потім я скористався нативною виконуваною програмою Win32 і зробив те саме завдання, просто скинувши файл шлях, модифікований та розмір до консолі та виконаний з Java. Швидкість була дивовижна. Нативний процес і обробка моїх рядків для читання даних може обробляти понад 1000 елементів в секунду.
Тож, хоча люди вниз оцінювали вищезазначений коментар, це правильне рішення і вирішило мою проблему. У моєму випадку я знав, що мені потрібні розміри папок раніше часу, і я міг передати це в командному рядку моєму додатку win32. Я йшов від годин, щоб обробити каталог до хвилин.
Проблема також видалася специфічною для Windows. У ОС X не було тієї самої проблеми, і вона могла отримати доступ до інформації про мережеві файли так само швидко, як і ОС могла це зробити.
Обробка файлів Java у Windows жахлива. Доступ до локальних дисків до файлів - це нормально. Саме жахливі показники спричинили саме мережеві акції. Windows може отримати інформацію про загальну частку мережі та обчислити загальний розмір також за хвилину.
--Бен
Якщо ви хочете розмір файлу декількох файлів у каталозі, використовуйте Files.walkFileTree
. Розмір можна отримати зBasicFileAttributes
що отримаєте.
Це набагато швидше, ніж виклик .length()
результату File.listFiles()
або використання Files.size()
в результаті Files.newDirectoryStream()
. У моїх тестових випадках це було приблизно в 100 разів швидше.
Files.walkFileTree
доступний на Android 26+.
Власне, я думаю, що "ls" може бути швидшим. У Java напевно є деякі проблеми, які стосуються отримання інформації про файл. На жаль, не існує еквівалентного безпечного методу рекурсивних ls для Windows. (DIR / S cmd.exe може заплутатися та генерувати помилки в нескінченних петлях)
У XP, для доступу до сервера в локальній мережі, у Windows мені знадобиться 5 секунд, щоб отримати кількість файлів у папці (33 000) та загальний розмір.
Коли я повторюю рекурсивно через це на Java, це займає у мене більше 5 хвилин. Я почав вимірювати час, необхідний для виконання file.length (), file.lastModified () і file.toURI (), і я знайшов, що 99% мого часу займає ці 3 виклики. 3 дзвінки, які мені справді потрібно зробити ...
Різниця для 1000 файлів становить 15 мс локального порівняно з 1800 мс на сервері. Сканування шляху до сервера на Java смішно повільне. Якщо рідна ОС може швидко просканувати ту саму папку, чому не може Java?
Як більш повний тест, я використав WineMerge на XP, щоб порівняти модифіковану дату та розмір файлів на сервері та локальних файлів. Це повторювало повне дерево директорій із 33 000 файлів у кожній папці. Загальний час, 7 секунд. java: понад 5 хвилин.
Тож оригінальне твердження та запитання від ОП є правдивим та дійсним. Його менш помітно при роботі з локальною файловою системою. Місцеве порівняння папки з 33000 предметами займає 3 секунди в WinMerge, а на Яві - 32 секунди. Отже, ява проти рідного - це 10-кратне уповільнення цих рудиментарних тестів.
Java 1.6.0_22 (остання), гігабітна локальна мережа та мережеві з'єднання, ping менше 1 мс (обидва в одному комутаторі)
Java повільна.
З еталону GHad, деякі люди згадували:
1> Як згадував BalusC: у цьому випадку передається stream.available ().
Оскільки доступний () повертає оцінку кількості байтів, які можна прочитати (або пропустити) з цього вхідного потоку, не блокуючи наступним викликом методу для цього вхідного потоку.
Тож перше, щоб видалити URL цього підходу.
2> Як згадував StuartH - порядок тестового запуску також змінює кеш-пам'ять, тому вийміть це, запустивши тест окремо.
Тепер почніть тест:
Коли CHANNEL запускається один:
CHANNEL sum: 59691, per Iteration: 238.764
Коли LENGTH запускається один:
LENGTH sum: 48268, per Iteration: 193.072
Так виглядає, що LENGTH є переможцем тут:
@Override
public long getResult() throws Exception {
File me = new File(FileSizeBench.class.getResource(
"FileSizeBench.class").getFile());
return me.length();
}