Вопрос

Во время поиска в Google я вижу, что использование 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;
    }

}

Другие советы

Тест, предоставленный GHad, помимо определения длины измеряет множество других вещей (таких как отражение, создание экземпляров объектов и т. д.).Если мы попытаемся избавиться от этих вещей, то для одного вызова я получу следующее время в микросекундах:

   file sum___19.0, per Iteration___19.0
    raf sum___16.0, per Iteration___16.0
channel sum__273.0, per Iteration__273.0

Для 100 прогонов и 10000 итераций я получаю:

   file sum__1767629.0, per Iteration__1.7676290000000001
    raf sum___881284.0, per Iteration__0.8812840000000001
channel sum___414286.0, per Iteration__0.414286

Я запустил следующий модифицированный код, указав в качестве аргумента имя файла размером 100 МБ.

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)

Если все, что вам нужно, это размер файла, file.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.В OS X не было такой проблемы, и она могла получить доступ к информации о сетевых файлах так же быстро, как и сама ОС.

Обработка файлов Java в Windows ужасна.Однако доступ к локальному диску для файлов в порядке.Именно общие сетевые ресурсы стали причиной ужасной производительности.Windows также могла получить информацию о сетевом ресурсе и рассчитать его общий размер менее чем за минуту.

--Бен

Если вам нужен размер нескольких файлов в каталоге, используйте Files.walkFileTree.Размер можно узнать по ссылке BasicFileAttributes что вы получите.

Это намного быстрее, чем звонить .length() по результату File.listFiles() или используя Files.size() по результату Files.newDirectoryStream().В моих тестовых случаях это было примерно в 100 раз быстрее.

На самом деле, я думаю, что «ls» может быть быстрее.В Java определенно есть некоторые проблемы, связанные с получением информации о файле.К сожалению, для Windows не существует эквивалентного безопасного метода рекурсивного ls.(DIR /S cmd.exe может путаться и генерировать ошибки в бесконечных циклах)

В XP при доступе к серверу в локальной сети мне в Windows требуется 5 секунд, чтобы получить количество файлов в папке (33 000) и общий размер.

Когда я рекурсивно повторяю это на Java, это занимает у меня более 5 минут.Я начал измерять время, необходимое для выполнения file.length(), file.lastModified() и file.toURI(), и обнаружил, что 99% моего времени занимают эти три вызова.3 звонка, которые мне действительно нужно сделать...

Разница для 1000 файлов составляет 15 мс локально против 1800 мс на сервере.Сканирование путей к серверу в Java невероятно медленное.Если родная ОС может быстро сканировать ту же папку, почему Java не может этого сделать?

В качестве более полного теста я использовал WineMerge на XP, чтобы сравнить дату изменения и размер файлов на сервере с файлами локально.Это происходило по всему дереву каталогов, состоящему из 33 000 файлов в каждой папке.Общее время 7 секунд.Джава:более 5 минут.

Таким образом, исходное утверждение и вопрос ФП верны и действительны.Это менее заметно при работе с локальной файловой системой.Выполнение локального сравнения папки с 33 000 элементов занимает 3 секунды в WinMerge и 32 секунды локально в Java.Итак, еще раз, Java по сравнению с нативным кодом замедляет работу в 10 раз в этих элементарных тестах.

Java 1.6.0_22 (последняя версия), Gigabit LAN и сетевые подключения, пинг менее 1 мс (оба в одном коммутаторе)

Ява медленная.

Судя по тесту GHad, люди упомянули несколько проблем:

1>Как упомянул BalusC:В этом случае выполняется потокstream.available().

Поскольку доступный() возвращает оценивать количества байтов, которые можно прочитать (или пропустить) из этого входного потока без блокировки при следующем вызове метода для этого входного потока.

Итак, первым делом нужно удалить URL-адрес этого подхода.

2> Как упомянул StuartH, порядок выполнения теста также влияет на разницу в кеше, поэтому уберите это, запустив тест отдельно.


Теперь начните тест:

Когда CHANNEL 1 запускается один:

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();
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top