Java отримати розмір файлу ефективно


166

Під час googling я бачу, що використання java.io.File#length()може бути повільним. FileChannelє також size()доступний метод.

Чи є ефективний спосіб у Java отримати розмір файлу?


7
чи можете ви надати посилання, що File.length () "може бути повільним"?
matt b

1
Вибачте, ось посилання javaperformancetuning.com/tips/rawtips.shtml шукайте "Інформація про файл, наприклад, File.length (), вимагає системного виклику і може бути повільною." це дійсно заплутане твердження, здається, майже припускається, що це буде системний виклик.
joshjdevl

25
Отримання довжини файлу вимагатиме системного виклику незалежно від того, як це робити. Це може бути повільним, якщо його через мережу або якусь іншу дуже повільну файлову систему. Немає більш швидкого способу отримати його, ніж File.length (), і визначення "повільного" тут просто означає, що не називати це зайвим.
веселість

Я думаю, що це GHad намагався перевірити нижче. Мої результати (на ubuntu 8.04): найпростіша лише одна URL-адреса доступу. 5 запусків, 50 ітерацій CHANNEL - це найшвидше заплутане? :) для моїх цілей я просто робитиму один доступ. хоча це дивно? що ми отримали різні результати
joshjdevl

1
Ця операція може бути дуже повільною, якщо інформація знаходиться на диску, а не в кеші. (як, наприклад, у 1000 разів повільніше), однак для цього мало що можна зробити, крім того, щоб інформація, яка вам потрібна, завжди була в кеш-пам'яті (наприклад, попереднє завантаження її та наявність достатньої кількості пам'яті, щоб вона залишалася в пам'яті)
Пітер Лоурі

Відповіді:


102

Ну, я спробував виміряти це за допомогою коду нижче:

Для запусків = 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;
    }

}

1
Здається, що спосіб URL-адреси найкращий для отримання єдиного доступу, будь то XP або Linux. Greetz GHad
GHad

73
stream.available()не повертає довжину файлу. Він повертає кількість байтів, доступних для читання, не блокуючи інших потоків. Не обов’язково така ж кількість байтів, як довжина файлу. Щоб отримати реальну довжину від потоку, вам дійсно потрібно його прочитати (а тим часом порахувати прочитані байти).
BalusC

11
Цей орієнтир є, а точніше, його тлумачення є невірним. При малому рахунку ітерації пізніші тести скористаються кешуванням файлів операційної системи. У тесті на вищі ітерації рейтинг правильний, але не тому, що File.length () кешує щось, а просто тому, що інші 2 варіанти базуються на тому ж методі, але роблять додаткову роботу, що їх сповільнює.
x4u

2
@Paolo, кешування та оптимізація доступу до файлової системи - одна з головних обов'язків ОС. faqs.org/docs/linux_admin/buffer-cache.html Щоб отримати хороші результати бенчмаркінгу, кеш слід очистити перед кожним запуском.
z0r

3
Крім того, що говорить javadoc для InputStream.available (), той факт, що доступний () метод повертає int, повинен бути червоним прапором проти URL-підходу. Спробуйте його з файлом об'ємом 3 ГБ, і очевидно, що це невірний спосіб визначення довжини файлу.
Скрубі

32

Орієнтир, який дає 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));
    }
  }
}

3
насправді, хоча ви вірно говорите, що він вимірює інші аспекти, я повинен бути більш чітким у своєму питанні. Я хочу отримати розмір файлу з декількох файлів, і мені хочеться якнайшвидшого способу. тож мені справді потрібно враховувати створення об’єктів та накладні витрати, оскільки це справжній сценарій
joshjdevl

3
Близько 90% часу проводиться в цій справі getResource. Я сумніваюся, що вам потрібно використовувати відображення, щоб отримати ім'я файлу, який містить деякий байт-код Java.

20

Усі тестові випадки в цій публікації є помилковими, оскільки вони отримують доступ до одного файлу для кожного тестованого методу. Отже, кешування диска починає керуватися тестами 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

9

Коли я змінюю ваш код, щоб використовувати файл, доступ до якого має абсолютний шлях замість ресурсу, я отримую інший результат (за 1 пробіг, 1 ітерацію та файл 100 000 байт - рази для 10 байтного файлу ідентичні 100 000 байтам )

Сума довжини: 33, за повторення: 33,0

Сума КАНАЛУ: 3626, за повторення: 3626,0

Сума URL-адрес: 294, за повторення: 294.0


9

У відповідь на орієнтир 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";
        } 
    }
}

8

Я зіткнувся з цим самим питанням. Мені потрібно було отримати розмір файлу та модифіковану дату 90 000 файлів на загальній мережі. Використовуючи Java та будучи максимально мінімалістичним, це зайняло б дуже багато часу. (Мені потрібно було отримати URL-адресу з файлу, а також шлях об'єкта. Отже, його дещо варіювалося, але більше години.) Потім я скористався нативною виконуваною програмою Win32 і зробив те саме завдання, просто скинувши файл шлях, модифікований та розмір до консолі та виконаний з Java. Швидкість була дивовижна. Нативний процес і обробка моїх рядків для читання даних може обробляти понад 1000 елементів в секунду.

Тож, хоча люди вниз оцінювали вищезазначений коментар, це правильне рішення і вирішило мою проблему. У моєму випадку я знав, що мені потрібні розміри папок раніше часу, і я міг передати це в командному рядку моєму додатку win32. Я йшов від годин, щоб обробити каталог до хвилин.

Проблема також видалася специфічною для Windows. У ОС X не було тієї самої проблеми, і вона могла отримати доступ до інформації про мережеві файли так само швидко, як і ОС могла це зробити.

Обробка файлів Java у Windows жахлива. Доступ до локальних дисків до файлів - це нормально. Саме жахливі показники спричинили саме мережеві акції. Windows може отримати інформацію про загальну частку мережі та обчислити загальний розмір також за хвилину.

--Бен


3

Якщо ви хочете розмір файлу декількох файлів у каталозі, використовуйте Files.walkFileTree. Розмір можна отримати зBasicFileAttributes що отримаєте.

Це набагато швидше, ніж виклик .length()результату File.listFiles()або використання Files.size()в результаті Files.newDirectoryStream(). У моїх тестових випадках це було приблизно в 100 разів швидше.


FYI, Files.walkFileTreeдоступний на Android 26+.
Джошуа Пінтер

2

Власне, я думаю, що "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 повільна.


2
Це також видається специфічним для ОС. Зробити те саме додаток java за тією ж папкою в OS X за допомогою samba знадобилося 26 секунд, щоб перерахувати всі 33 000 елементів, розмірів та дат. Тож мережа Java просто повільна в Windows тоді? (OS X також був java 1.6.0_22.)
Бен Спінк

2

З еталону 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();
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.