2008-09-22 4 views
153

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

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

+7

можете ли вы предоставить ссылки, говорящие, что File.length() «может быть медленным»? – 2008-09-22 19:02:37

+1

извините, вот ссылка http://www.javaperformancetuning.com/tips/rawtips.shtml Поиск для «Файловая информация, такая как File.length() требует системного вызова и может быть медленной». Это действительно запутанное утверждение, кажется, почти предполагается, что это будет системный вызов. – joshjdevl 2008-09-22 19:53:25

+24

Получение длины файла потребует системного вызова независимо от того, как вы это делаете. Это может быть медленным, если оно связано с сетью или какой-либо другой очень медленной файловой системой. Существует не более быстрый способ получить его, чем File.length(), и определение «медленное» здесь просто означает, что это не обязательно. – jsight 2008-09-22 20:18:12

ответ

95

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

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

} 
9

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

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

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

URL-адрес: 294, за итерацию: 294.0

31

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

В ответ на rgrig-х, время, необходимое для открытия/закрытия экземпляры RandomAccessFile FileChannel & также необходимо принимать во внимание, так как эти классы будут открывать поток для чтения файла.

После изменения теста, я получил эти результаты за 1 итерации на более 85Mb файл:

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"; 
     } 
    } 
} 
2

На самом деле, я думаю, что «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 минут.

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

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

Java медленно.

16

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

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

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

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

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

--Ben

2

Из теста GHAD, есть несколько вопрос люди упоминали:

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

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

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

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


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

Когда CHANNEL один прогон в одиночку:

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(); 
} 
3

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

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