java obtém o tamanho do arquivo de forma eficiente

Enquanto googling, vejo que usando java.io.File#length() pode ser lento. FileChannel tem um método size() que também está disponível.

Existe uma maneira eficiente em java para obter o tamanho do arquivo?

Bem, eu tentei medir isso com o código abaixo:

Para runs = 1 e iterations = 1, o método de URL é mais rápido, seguido pelo canal. Eu corro isso com alguma pausa fresca cerca de 10 vezes. Então, para um único access, usar a URL é a maneira mais rápida de pensar:

 LENGTH sum: 10626, per Iteration: 10626.0 CHANNEL sum: 5535, per Iteration: 5535.0 URL sum: 660, per Iteration: 660.0 

Para runs = 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 a 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 durations = new EnumMap(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 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; } } 

O benchmark dado por GHad mede muitas outras coisas (como reflection, instanciação de objects, etc.) além de obter o tamanho. Se tentarmos nos livrar dessas coisas, então, em uma chamada, recebo os seguintes horários em microssegundos:

    sum do arquivo ___ 19,0, por Iteração ___ 19,0
     raf sum ___ 16,0, por Iteração ___ 16,0
 sum do canal__273.0, por Iteração__273.0

Para 100 execuções e 10000 iterações eu recebo:

    arquivo sum__1767629.0, por Iteração__1.7676290000000001
     raf sum ___ 881284.0, por Iteração__0.8812840000000001
 sum do canal ___ 414286,0, por Iteração__0,414286

Eu executei o seguinte código modificado, dando como argumento o nome de um arquivo de 100MB.

 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 times = new HashMap(); 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 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 neste post são falhos quando acessam o mesmo arquivo para cada método testado. Então, o cache de disco é acionado, do qual os testes 2 e 3 se beneficiam. Para provar meu ponto, tomei o caso de teste fornecido pelo GHAD e mudei a ordem de enumeração e abaixo estão os resultados.

Olhando para o resultado acho que File.length () é o vencedor realmente.

Ordem de teste é a ordem de saída. Você pode até ver o tempo gasto na minha máquina variou entre as execuções, mas File.Length () quando não primeiro, e incorrer em primeiro access ao disco ganhou.

 --- 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 de um arquivo de 10 bytes são idênticos a 100.000 bytes )

Soma de COMPRIMENTO: 33, por Iteração: 33,0

CHANNEL sum: 3626, por Iteração: 3626.0

Soma de URL: 294, por Iteração: 294,0

Em resposta ao benchmark do rgrig, o tempo necessário para abrir / fechar as instâncias FileChannel & RandomAccessFile também precisa ser levado em conta, pois essas classs abrirão um stream para ler o arquivo.

Depois de modificar o benchmark, obtive esses resultados para 1 iteração em um arquivo de 85MB:

 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 que você precisa é o tamanho do arquivo, file.length () é a maneira mais rápida de fazer isso. Se você pretende usar o arquivo para outros fins, como leitura / escrita, a RAF parece ser uma aposta melhor. Apenas não 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 times = new HashMap(); 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 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 me deparei com esse mesmo problema. Eu precisava obter o tamanho do arquivo e modificar a data de 90.000 arquivos em um compartilhamento de rede. Usando Java, e sendo o mais minimalista possível, levaria muito tempo. (Eu precisava obter o URL do arquivo, e o caminho do object também. Então seu variou um pouco, mas mais de uma hora.) Então eu usei um executável Win32 nativo, e fiz a mesma tarefa, apenas despejando o arquivo caminho, modificado e tamanho para o console e executou isso de 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.

Assim, mesmo que as pessoas de baixo classificassem o comentário acima, esta é uma solução válida e resolveu meu problema. No meu caso, eu conhecia as pastas que eu precisava antes do tempo, e eu poderia passar isso na linha de comando para o meu aplicativo win32. Eu fui de horas para processar um diretório para minutos.

O problema também parece ser específico do Windows. O OS X não tinha o mesmo problema e podia acessar as informações do arquivo de rede tão rápido quanto o sistema operacional pudesse fazê-lo.

Manipulação de arquivos Java no Windows é terrível. O access local ao disco para arquivos é bom. Foram apenas compartilhamentos de rede que causaram o terrível desempenho. O Windows pode obter informações sobre o compartilhamento de rede e calcular o tamanho total em menos de um minuto também.

–Ben

Se você quiser o tamanho do arquivo de vários arquivos em um diretório, use Files.walkFileTree . Você pode obter o tamanho dos BasicFileAttributes que você receberá.

Isso é muito mais rápido, em seguida, chamando .length() no resultado de File.listFiles() ou usando Files.size() no resultado de Files.newDirectoryStream() . Nos meus casos de teste, foi cerca de 100 vezes mais rápido.

Na verdade, acho que o “ls” pode ser mais rápido. Definitivamente, existem alguns problemas em Java lidando com a obtenção de informações sobre o arquivo. Infelizmente não existe um método seguro equivalente de ls recursivo para o Windows. (DIR / S do 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 eu iterativo recursivamente através deste em Java, isso me leva mais de 5 minutos. Eu comecei a medir o tempo que leva para fazer file.length (), file.lastModified () e file.toURI () eo que eu descobri é que 99% do meu tempo é tomado por essas 3 chamadas. As 3 chamadas que preciso fazer …

A diferença para 1000 arquivos é 15ms local versus 1800ms no servidor. A varredura do caminho do servidor em Java é ridiculamente lenta. Se o sistema operacional nativo pode ser rápido na digitalização dessa mesma pasta, por que não pode Java?

Como um teste mais completo, usei o WineMerge no XP para comparar a data modificada e o tamanho dos arquivos no servidor versus os arquivos localmente. Isso foi uma iteração em toda a tree de diretórios de 33.000 arquivos em cada pasta. Tempo total, 7 segundos. java: mais de 5 minutos.

Portanto, a declaração original e a pergunta do OP são verdadeiras e válidas. É menos perceptível quando se lida com um sistema de arquivos local. Fazer uma comparação local da pasta com 33.000 itens leva 3 segundos no WinMerge e leva 32 segundos localmente em Java. Então, novamente, java versus native é 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 1 ms (ambos no mesmo comutador)

Java está lento.

A partir do benchmark de GHad, há alguns assuntos que as pessoas mencionaram:

1> Como BalusC mencionado: stream.available () é fluida neste caso.

Porque available () retorna uma estimativa do número de bytes que podem ser lidos (ou ignorados) deste stream de input sem serem bloqueados pela próxima chamada de um método para este stream de input.

Então, primeiro para remover o URL dessa abordagem.

2> Como StuartH mencionou – a ordem da execução do teste também faz a diferença do cache, então retire esse teste executando o teste separadamente.


Agora inicie o teste:

Quando o CANAL é executado sozinho:

 CHANNEL sum: 59691, per Iteration: 238.764 

Quando LENGTH é executado sozinho:

 LENGTH sum: 48268, per Iteration: 193.072 

Então parece que o LENGTH é o vencedor aqui:

 @Override public long getResult() throws Exception { File me = new File(FileSizeBench.class.getResource( "FileSizeBench.class").getFile()); return me.length(); }