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

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

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

Ответ 1

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

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

}

Ответ 2

Тест, заданный 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));
    }
  }
}

Ответ 3

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

Ответ 4

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

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

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

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

Ответ 5

В ответ на тест 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";
        } 
    }
}

Ответ 6

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

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

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

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

- Бен

Ответ 7

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

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

Ответ 8

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

В 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 минут.

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

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

Java медленная.

Ответ 9

Из теста 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();
}