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

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

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


person joshjdevl    schedule 22.09.2008    source источник
comment
можете ли вы предоставить ссылки, в которых говорится, что File.length () может работать медленно?   -  person matt b    schedule 22.09.2008
comment
извините, вот ссылка javaperformancetuning.com/tips/rawtips.shtml поиск файла такая информация, как File.length (), требует системного вызова и может работать медленно. это действительно сбивающее с толку утверждение, кажется, почти предполагается, что это будет системный вызов.   -  person joshjdevl    schedule 22.09.2008
comment
Для получения длины файла потребуется системный вызов, независимо от того, как вы это делаете. Он может быть медленным, если он работает по сети или какой-либо другой очень медленной файловой системе. Нет более быстрого способа получить его, чем File.length (), и определение медленного здесь просто означает, что не вызывайте его без надобности.   -  person jsight    schedule 23.09.2008
comment
Я думаю, что это то, что GHad пытался проверить ниже. Мои результаты (на ubuntu 8.04): только один URL-адрес доступа является самым быстрым. 5 прогонов, 50 итераций. КАНАЛ еще не самый быстрый запутанный? :) для моих целей я просто сделаю один доступ. хоть это странно? что у нас разные результаты   -  person joshjdevl    schedule 23.09.2008
comment
Эта операция может быть очень медленной, если информация находится на диске, а не в кеше. (например, в 1000 раз медленнее), однако вы мало что можете с этим поделать, кроме обеспечения того, чтобы информация, которая вам нужна, всегда была в кеше (например, ее предварительная загрузка и наличие достаточного количества памяти, чтобы она оставалась в памяти)   -  person Peter Lawrey    schedule 09.10.2010
comment
Я бы поставил под сомнение обоснованность использования документа, которому уже исполнилось 8/9 лет к тому времени, когда был задан этот вопрос, в качестве источника рекомендаций по оптимизации.   -  person Burhan Ali    schedule 09.12.2013
comment
Есть более быстрый образец (только java-7) stackoverflow.com/a/19877372/644140   -  person Capy    schedule 16.12.2013
comment
В редких случаях, когда вы пользуетесь Android, обратите внимание на StatFs. Он использует статистику файловой системы и почти в 1000 раз быстрее, чем рекурсивные методы. Нашу реализацию можно найти здесь: stackoverflow.com/a/58418639/293280   -  person Joshua Pinter    schedule 16.10.2019


Ответы (9)


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

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

}
person GHad    schedule 22.09.2008
comment
интересно, вот мои результаты (ubuntu 8.04) Сумма ДЛИНЫ: 97442, на итерацию: 97442.0 Сумма КАНАЛОВ: 15789, на итерацию: 15789.0 Сумма URL: 522, на итерацию: 522.0 Сумма ДЛИНЫ: 127074, на итерацию: 508.296 Сумма КАНАЛА: 51582 , на итерацию: 206,328 Сумма URL: 61334, на итерацию: 245,336 - person joshjdevl; 23.09.2008
comment
Похоже, что путь URL - лучший вариант для единого доступа, будь то XP или Linux. Greetz GHad - person GHad; 23.09.2008
comment
stream.available() не возвращает длину файла. Он возвращает количество байтов, доступных для чтения, без блокировки других потоков. Это не обязательно то же количество байтов, что и длина файла. Чтобы получить реальную длину потока, вам действительно нужно его прочитать (и при этом посчитать прочитанные байты). - person BalusC; 26.11.2009
comment
Хороший момент, и вы правы, но я никогда не испытывал разницы для файлов, так как я ожидаю, что все байты будут доступны для чтения, когда я хочу прочитать файл таким образом. По крайней мере, если размер меньше Integer.MAX_VALUE - person GHad; 26.11.2009
comment
@GHad тогда ты делаешь это неправильно. В API нет ничего, что определяло бы такое поведение. Вы полагаетесь на удачу. - person user207421; 02.06.2011
comment
Этот тест, а точнее его интерпретация, неверен. При малом количестве итераций более поздние тесты используют кеширование файлов операционной системы. В тесте с более высокими итерациями рейтинг правильный, но не потому, что File.length () что-то кеширует, а просто потому, что другие 2 варианта основаны на том же методе, но выполняют дополнительную работу, которая их замедляет. - person x4u; 02.06.2011
comment
Я действительно не думаю, что системный вызов можно кэшировать, как Java узнает, когда размер файла изменился? - person Paolo; 25.11.2011
comment
@Paolo, кэширование и оптимизация доступа к файловой системе - одна из основных задач ОС. faqs.org/docs/linux_admin/buffer-cache.html Чтобы Для получения хороших результатов тестирования необходимо очищать кеш перед каждым запуском. - person z0r; 06.07.2012
comment
@ z0r в ответе сказано, что java кэширует системный вызов, а не то, что os кэширует системные вызовы. - person Paolo; 10.07.2012
comment
Хотя эти цифры в определенной степени интересны, я не уверен, что они все настолько полезны без более глубокого понимания того, что именно происходит на каждом этапе пути. Это ненастоящий вариант использования, и на тестирование нескольких способов доступа к информации, требующих быстрого последовательного чтения с диска без очистки всех возможных кешей между кодом и жестким диском, неизбежно будут чрезмерно влиять непредсказуемые факторы. Микро-тесты полны подводных камней. - person Thor84no; 05.11.2012
comment
Likc BalusC упомянул: в этом случае используется stream.available (). Поскольку available () возвращает оценку количества байтов, которые могут быть прочитаны (или пропущены) из этого входного потока без блокировки при следующем вызове метода для этого входного потока. - person Gob00st; 17.10.2013
comment
Помимо того, что говорится в javadoc для InputStream.available (), тот факт, что метод available () возвращает int, должен быть красным флажком против подхода URL. Попробуйте это с файлом размером 3 ГБ, и станет очевидно, что это неправильный способ определения длины файла. - person Scrubbie; 30.05.2014
comment
Означает ли это, что file.length также читает весь файл в память? Мой вопрос о скорости: будет ли os сохранять длину файла в качестве параметра, или вам нужно загрузить весь файл в память jvm, чтобы получить его размер? - person eddyP23; 04.08.2017

Тест, предоставленный 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));
    }
  }
}
person Community    schedule 23.09.2008
comment
на самом деле, хотя вы правы, говоря, что он измеряет другие аспекты, я должен быть более ясным в своем вопросе. Я хочу получить размер нескольких файлов, и мне нужен самый быстрый способ. поэтому мне действительно нужно учитывать создание объекта и накладные расходы, так как это реальный сценарий - person joshjdevl; 24.09.2008
comment
Около 90% времени тратится на getResource. Я сомневаюсь, что вам нужно использовать отражение, чтобы получить имя файла, содержащего некоторый байт-код Java. - person ; 26.09.2008

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

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

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

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

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

person tgdavies    schedule 23.09.2008

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

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

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

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

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

--Бен

person Ben Spink    schedule 02.04.2011

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

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

person Scg    schedule 23.01.2014
comment
К вашему сведению, Files.walkFileTree доступен на Android 26+. - person Joshua Pinter; 15.10.2019

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

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

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

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

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

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

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

Java работает медленно.

person Ben Spink    schedule 17.11.2010
comment
Это также похоже на ОС. Выполнение того же java-приложения, идущего после той же папки из OS X с использованием самбы, потребовало 26 секунд, чтобы перечислить все 33000 элементов, размеров и дат. Значит, сетевая Java работает медленно в Windows? (OS X также была java 1.6.0_22.) - person Ben Spink; 17.11.2010

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

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

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

Итак, 1-й, чтобы удалить URL-адрес этого подхода.

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


Теперь приступим к тесту:

Когда CHANNEL one работает в одиночку:

CHANNEL sum: 59691, per Iteration: 238.764

Когда ДЛИНА одного пробега:

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();
}
person Gob00st    schedule 17.10.2013