java obter tamanho do arquivo com eficiência

166

Enquanto pesquisava no Google, vejo que o uso java.io.File#length()pode ser lento. FileChanneltambém tem um size()método disponível.

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

joshjdevl
fonte
7
você pode fornecer os links dizendo que File.length () "pode ​​ser lento"?
matt b
1
desculpe, aqui está o link javaperformancetuning.com/tips/rawtips.shtml procure por "Informações sobre o arquivo, como File.length (), requerem uma chamada do sistema e podem ser lentas." é realmente uma afirmação confusa, parece quase assumido que seria uma chamada de sistema.
joshjdevl 22/09/08
25
Obter o tamanho do arquivo exigirá uma chamada do sistema, não importa como você o faça. Pode ser lento se estiver em uma rede ou em algum outro sistema de arquivos muito lento. Não há maneira mais rápida de obtê-lo do que File.length (), e a definição de "lento" aqui significa apenas não chamá-lo desnecessariamente.
jsight
Eu acho que é isso que o GHad estava tentando testar abaixo. Meus resultados são (no ubuntu 8.04): apenas uma URL de acesso é mais rápida. 5 execuções, 50 iterações O CHANNEL é o mais confuso ainda? :) para os meus propósitos, porém, eu apenas estarei fazendo um acesso. embora seja estranho? que obtivemos resultados diferentes
joshjdevl 22/09/08
1
Esta operação pode ser muito lenta se as informações estiverem no disco e não no cache. (como 1000x mais lento), no entanto, há pouco que você pode fazer sobre isso além de assegurar a informação que você precisa está sempre em cache (como pré carregá-lo e ter memória suficiente para que fique na memória)
Peter Lawrey

Respostas:

102

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

}
GHad
fonte
1
Parece que o caminho da URL é o melhor caminho para o acesso único, seja XP ou Linux. Greetz GHad
GHad 22/09/08
73
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).
BalusC
11
Esta referência é ou melhor, sua interpretação não está correta. Na contagem de iterações baixa, os testes posteriores aproveitam o cache de arquivos do sistema operacional. No teste de iterações mais altas, a classificação está correta, mas não porque File.length () está armazenando em cache alguma coisa, mas simplesmente porque as outras 2 opções são baseadas no mesmo método, mas fazem um trabalho extra que as atrasa.
X4u
2
@Paolo, armazenar em cache e otimizar o acesso ao sistema de arquivos é uma das principais responsabilidades de um sistema operacional. faqs.org/docs/linux_admin/buffer-cache.html Para obter bons resultados de benchmarking, o cache deve ser limpo antes de cada execução.
Z0r 06/07/12
3
Além do que diz o javadoc para InputStream.available (), o fato de o método available () retornar um int deve ser um sinalizador vermelho na abordagem de URL. Experimente com um arquivo de 3 GB e será óbvio que não é uma maneira válida de determinar o tamanho do arquivo.
Scrubbie
32

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));
    }
  }
}
basilikode
fonte
3
na verdade, enquanto você estiver correto ao dizer que mede outros aspectos, devo ficar mais claro na minha pergunta. Estou procurando obter o tamanho de vários arquivos e quero a maneira mais rápida possível. então eu realmente necessidade de ter em conta a criação do objeto ea sobrecarga, uma vez que é um verdadeiro cenário
joshjdevl
3
Cerca de 90% do tempo é gasto nessa coisa getResource. Duvido que você precise usar reflexão para obter o nome de um arquivo que contenha algum código de código Java.
20

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
StuartH
fonte
9

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

tgdavies
fonte
9

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";
        } 
    }
}
Karthikeyan
fonte
8

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

Ben Spink
fonte
3

Se você deseja o tamanho de vários arquivos em um diretório, use Files.walkFileTree. Você pode obter o tamanho do BasicFileAttributesque 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.

Scg
fonte
FYI, Files.walkFileTreeestá disponível no Android 26+.
Joshua Pinter
2

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.

Ben Spink
fonte
2
Isso também parece ser específico do sistema operacional. Fazendo o mesmo aplicativo java seguindo a mesma pasta do OS X usando o samba, demorou 26 segundos para listar os 33.000 itens, tamanhos e datas inteiros. Então a rede Java é lenta no Windows, então? (OS X foi java 1.6.0_22 também.)
Ben Spink
2

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