Java получить размер файла эффективно - PullRequest
160 голосов
/ 22 сентября 2008

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

Есть ли эффективный способ в Java получить размер файла?

Ответы [ 9 ]

99 голосов
/ 22 сентября 2008

Ну, я попытался измерить это с помощью кода ниже:

Для прогонов = 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;
    }

}
32 голосов
/ 23 сентября 2008

Тест, данный 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));
    }
  }
}
17 голосов
/ 22 марта 2011

Все тестовые примеры в этом посте имеют недостатки, так как имеют доступ к одному и тому же файлу для каждого тестируемого метода. Таким образом, кеширование диска дает преимущества при тестах 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 голосов
/ 23 сентября 2008

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

ДЛИНА: 33, за итерацию: 33,0

сумма КАНАЛА: 3626, за итерацию: 3626.0

Сумма URL: 294, за итерацию: 294,0

8 голосов
/ 02 апреля 2011

Я столкнулся с этой же проблемой. Мне нужно было получить размер файла и дату изменения в 90000 файлов на сетевом ресурсе. Используя Java и будучи максимально минималистичным, это займет очень много времени. (Мне нужно было получить URL из файла, а также путь к объекту. Так что он несколько варьировался, но больше часа.) Затем я использовал собственный исполняемый файл Win32 и выполнил ту же задачу, просто выгрузив файл путь, измененный и размер к консоли и выполненный из Java. Скорость была потрясающая. Собственный процесс и обработка строк для чтения данных могут обрабатывать более 1000 элементов в секунду.

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

Проблема также, похоже, связана с Windows. OS X не имела такой же проблемы и могла получить доступ к информации о сетевых файлах так быстро, как это могла сделать ОС.

Java Работа с файлами в Windows ужасна. Локальный доступ к файлам для файлов все же хорошо. Это были просто сетевые ресурсы, которые вызвали ужасную производительность. Windows может получить информацию об общем сетевом ресурсе и вычислить общий размер за минуту.

- Ben

8 голосов
/ 26 ноября 2009

В ответ на тест 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";
        } 
    }
}
3 голосов
/ 23 января 2014

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

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

2 голосов
/ 17 октября 2013

Из теста GHad есть несколько проблем, о которых упоминали люди:

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

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

Итак, сначала удалите URL этого подхода.

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


Теперь запустите тест:

Когда КАНАЛ работает один:

CHANNEL sum: 59691, per Iteration: 238.764

При ДЛИННОМ беге в одиночку:

LENGTH sum: 48268, per Iteration: 193.072

Похоже, ДЛИНА - победитель:

@Override
public long getResult() throws Exception {
    File me = new File(FileSizeBench.class.getResource(
            "FileSizeBench.class").getFile());
    return me.length();
}
2 голосов
/ 17 ноября 2010

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

В XP при доступе к серверу в локальной сети у меня уходит 5 секунд в Windows, чтобы получить количество файлов в папке (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 минут.

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

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

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

...