Enquanto pesquisava no Google, vejo que o uso java.io.File#length()
pode ser lento.
FileChannel
também tem um size()
método disponível.
Existe uma maneira eficiente em java para obter o tamanho do arquivo?
Enquanto pesquisava no Google, vejo que o uso java.io.File#length()
pode ser lento.
FileChannel
também tem um size()
método disponível.
Existe uma maneira eficiente em java para obter o tamanho do arquivo?
Respostas:
Bem, tentei medir com o código abaixo:
Para execuções = 1 e iterações = 1, o método URL é mais rápido na maioria das vezes, seguido pelo canal. Eu corro isso com uma pausa fresca cerca de 10 vezes. Portanto, para acesso único, usar a URL é a maneira mais rápida em que consigo pensar:
LENGTH sum: 10626, per Iteration: 10626.0
CHANNEL sum: 5535, per Iteration: 5535.0
URL sum: 660, per Iteration: 660.0
Para execuções = 5 e iterações = 50, a imagem é diferente.
LENGTH sum: 39496, per Iteration: 157.984
CHANNEL sum: 74261, per Iteration: 297.044
URL sum: 95534, per Iteration: 382.136
O arquivo deve estar armazenando em cache as chamadas para o sistema de arquivos, enquanto os canais e URL possuem alguma sobrecarga.
Código:
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;
}
}
stream.available()
não retorna o tamanho do arquivo. Retorna a quantidade de bytes disponíveis para leitura sem bloquear outros fluxos. Não é necessariamente a mesma quantidade de bytes que o tamanho do arquivo. Para obter o comprimento real de um fluxo, você realmente precisa lê- lo (e contar os bytes de leitura enquanto isso).
O benchmark fornecido pelo GHad mede muitas outras coisas (como reflexão, instanciação de objetos etc.), além de obter o comprimento. Se tentarmos nos livrar dessas coisas, em uma ligação recebo os seguintes tempos em microssegundos:
soma do arquivo ___ 19.0, por Iteração ___ 19.0 soma raf ___ 16,0, por Iteração ___ 16,0 soma do canal__273.0, por Iteration__273.0
Para 100 execuções e 10000 iterações, recebo:
arquivo sum__1767629.0, por Iteration__1.7676290000000001 soma raf ___ 881284.0, por Iteração__0.8812840000000001 soma do canal ___ 414286.0, por Iteração__0.414286
Eu executei o seguinte código modificado, fornecendo como argumento o nome de um arquivo de 100 MB.
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));
}
}
}
Todos os casos de teste nesta postagem são falhos, pois acessam o mesmo arquivo para cada método testado. Portanto, o cache do disco é iniciado, no qual os testes 2 e 3 se beneficiam. Para provar meu argumento, peguei o caso de teste fornecido pelo GHAD e alterei a ordem da enumeração e abaixo estão os resultados.
Olhando para o resultado, acho que File.length () é realmente o vencedor.
Ordem de teste é a ordem de saída. Você pode até ver o tempo gasto na minha máquina variando entre as execuções, mas File.Length () quando não é o primeiro e incorrendo no primeiro acesso ao disco.
---
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
Quando modifico seu código para usar um arquivo acessado por um caminho absoluto em vez de um recurso, obtenho um resultado diferente (para 1 execução, 1 iteração e um arquivo de 100.000 bytes - os tempos para um arquivo de 10 bytes são idênticos a 100.000 bytes )
Soma COMPRIMENTO: 33, por Iteração: 33.0
Soma do CHANNEL: 3626, por Iteração: 3626.0
Soma do URL: 294, por Iteração: 294.0
Em resposta à referência do rgrig, o tempo necessário para abrir / fechar as instâncias FileChannel e RandomAccessFile também precisa ser levado em consideração, pois essas classes abrirão um fluxo para a leitura do arquivo.
Após modificar o benchmark, obtive esses resultados para 1 iterações em um arquivo de 85 MB:
file totalTime: 48000 (48 us)
raf totalTime: 261000 (261 us)
channel totalTime: 7020000 (7 ms)
Para 10000 iterações no mesmo arquivo:
file totalTime: 80074000 (80 ms)
raf totalTime: 295417000 (295 ms)
channel totalTime: 368239000 (368 ms)
Se tudo o que você precisa é do tamanho do arquivo, file.length () é a maneira mais rápida de fazer isso. Se você planeja usar o arquivo para outros fins, como leitura / gravação, o RAF parece ser uma aposta melhor. Só não se esqueça de fechar a conexão do arquivo :-)
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";
}
}
}
Eu encontrei esse mesmo problema. Eu precisava obter o tamanho do arquivo e a data modificada de 90.000 arquivos em um compartilhamento de rede. Usando Java, e sendo o mais minimalista possível, levaria muito tempo. (Eu precisava obter a URL do arquivo e também o caminho do objeto. Portanto, isso variou um pouco, mas mais de uma hora.) Em seguida, usei um executável nativo do Win32 e fiz a mesma tarefa, apenas descartando o arquivo caminho, modificado e tamanho para o console e o executou em Java. A velocidade foi incrível. O processo nativo e minha manipulação de strings para ler os dados podem processar mais de 1000 itens por segundo.
Portanto, mesmo que as pessoas classifiquem abaixo o comentário acima, essa é uma solução válida e resolveu meu problema. No meu caso, eu conhecia as pastas que precisava dos tamanhos antecipadamente e podia passar isso na linha de comando para o meu aplicativo win32. Passei de horas para processar um diretório para minutos.
O problema também parecia ser específico do Windows. O OS X não teve o mesmo problema e pôde acessar as informações do arquivo de rede tão rápido quanto o SO.
O manuseio de arquivos Java no Windows é terrível. O acesso ao disco local para arquivos é bom. Foram apenas os compartilhamentos de rede que causaram o desempenho terrível. O Windows também pode obter informações sobre o compartilhamento de rede e calcular o tamanho total em menos de um minuto.
--Ben
Se você deseja o tamanho de vários arquivos em um diretório, use Files.walkFileTree
. Você pode obter o tamanho do BasicFileAttributes
que receberá.
Isso é muito mais rápido do que chamar .length()
o resultado File.listFiles()
ou usar Files.size()
o resultado de Files.newDirectoryStream()
. Nos meus casos de teste, era cerca de 100 vezes mais rápido.
Files.walkFileTree
está disponível no Android 26+.
Na verdade, acho que os "ls" podem ser mais rápidos. Definitivamente, existem alguns problemas no Java que lidam com a obtenção de informações sobre arquivos. Infelizmente, não existe um método seguro equivalente de ls recursivo para Windows. (o DIR / S de cmd.exe pode ficar confuso e gerar erros em loops infinitos)
No XP, acessando um servidor na LAN, levo 5 segundos no Windows para obter a contagem dos arquivos em uma pasta (33.000) e o tamanho total.
Quando iteramos recursivamente isso em Java, levo mais de 5 minutos. Comecei a medir o tempo necessário para fazer file.length (), file.lastModified () e file.toURI () e o que descobri é que 99% do meu tempo é gasto por essas três chamadas. As 3 chamadas que eu realmente preciso fazer ...
A diferença para 1000 arquivos é 15ms local versus 1800ms no servidor. A verificação do caminho do servidor em Java é ridiculamente lenta. Se o sistema operacional nativo pode ser rápido na verificação dessa mesma pasta, por que o Java não pode?
Como um teste mais completo, usei o WineMerge no XP para comparar a data da modificação e o tamanho dos arquivos no servidor versus os arquivos localmente. Isso estava repetindo a árvore de diretórios inteira de 33.000 arquivos em cada pasta. Tempo total, 7 segundos. java: mais de 5 minutos.
Portanto, a declaração e a pergunta originais do OP são verdadeiras e válidas. É menos perceptível ao lidar com um sistema de arquivos local. Fazer uma comparação local da pasta com 33.000 itens leva 3 segundos no WinMerge e 32 segundos localmente em Java. Então, novamente, java versus nativo é uma desaceleração de 10x nesses testes rudimentares.
Java 1.6.0_22 (mais recente), LAN Gigabit e conexões de rede, o ping é menor que 1ms (ambos no mesmo switch)
Java é lento.
Do benchmark do GHad, existem algumas questões que as pessoas mencionaram:
1> Como BalusC mencionado: stream.available () é fluido neste caso.
Porque available () retorna uma estimativa do número de bytes que podem ser lidos (ou ignorados) desse fluxo de entrada sem bloquear pela próxima chamada de um método para esse fluxo de entrada.
Então, primeiro a remover a URL desta abordagem.
2> Como StuartH mencionou - a ordem em que o teste é executado também faz a diferença no cache, então faça isso executando o teste separadamente.
Agora inicie o teste:
Quando um canal é executado sozinho:
CHANNEL sum: 59691, per Iteration: 238.764
Quando COMPRIMENTO um é executado sozinho:
LENGTH sum: 48268, per Iteration: 193.072
Então parece que o COMPRIMENTO é o vencedor aqui:
@Override
public long getResult() throws Exception {
File me = new File(FileSizeBench.class.getResource(
"FileSizeBench.class").getFile());
return me.length();
}