Во время поиска в Google я вижу, что использование java.io.File#length()
может быть медленным.
FileChannel
имеет size()
метод, который доступен также.
Есть ли эффективный способ в java для получения размера файла?
Во время поиска в 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)
Если вам нужен только размер файла, файл .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 есть определенные проблемы, связанные с получением информации о файле. К сожалению, нет эквивалентного безопасного метода рекурсивных 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 медленная.
Из теста 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();
}