Pergunta

Enquanto googling, eu vejo que o uso de java.io.File#length() pode ser lento. FileChannel tem um size() método "http://docs.oracle.com/javase/6/docs/api/java/nio/channels/FileChannel.html#size%28%29" que está disponível também.

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

Foi útil?

Solução

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

= Para corridas 1 e iterações = 1 o método de URL é mais rápido na maioria das vezes seguida de canal. Eu corro isso com alguns frescos pausa cerca de 10 vezes. Assim, para um acesso em tempo, usando a URL é a maneira mais rápida que eu posso pensar de:

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 chama 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 ser cache as chamadas para o sistema de arquivos, enquanto os canais e URL tem 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;
    }

}

Outras dicas

O valor de referência dado pela Ghad mede muitas outras coisas (como reflexão, objetos instanciar, etc.) além de obter o comprimento. Se tentar se livrar dessas coisas depois de uma chamada eu recebo os seguintes tempos em microssegundos:

   file sum___19.0, per Iteration___19.0
    raf sum___16.0, per Iteration___16.0
channel sum__273.0, per Iteration__273.0

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

   file sum__1767629.0, per Iteration__1.7676290000000001
    raf sum___881284.0, per Iteration__0.8812840000000001
channel sum___414286.0, per Iteration__0.414286

Eu fiz execute 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<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 neste post são falhos como eles acessar o mesmo arquivo para cada método testado. Então chutes de cache de disco em que os testes 2 e 3 se beneficiar. Para provar meu ponto Tomei caso de teste fornecido pelo Ghad e mudou a ordem de enumeração e abaixo estão os resultados.

Olhando resultado Acho file.length () é o vencedor realmente.

Ordem de teste é a ordem de saída. Você pode até mesmo ver o tempo gasto na minha máquina variou entre execuções, mas file.length () quando não estiver em primeiro lugar, e incorrer em primeiro won 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 eu modificar seu código para usar um arquivo acessado por um caminho absoluto, em vez de um recurso, recebo um resultado diferente (para 1 prazo, uma iteração, e um arquivo de 100.000 bytes - vezes para um arquivo de 10 bytes são idênticos a 100.000 bytes)

sum COMPRIMENTO: 33, por iteração: 33,0

CHANNEL soma: 3626, por iteração: 3626,0

URL soma: 294, por iteração: 294,0

Em resposta a referência do rgrig, o tempo necessário para fechar as instâncias abertas / FileChannel & RandomAccessFile também precisa ser levado em conta, como essas classes irão abrir um fluxo para ler o arquivo.

Depois de modificar o benchmark, eu tenho esses resultados para 1 iterações 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 fazê-lo. Se você pretende usar o arquivo para outros fins, como leitura / escrita, em seguida, RAF parece ser uma aposta melhor. Só não se esqueça de fechar a conexão ficheiro: -)

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

Corri para este mesmo problema. I necessário para obter o tamanho do arquivo e data de modificação de 90.000 arquivos em um compartilhamento de rede. Usando Java, e sendo tão minimalista quanto possível, levaria um tempo muito longo. (I necessário para obter o URL do arquivo e o caminho do objeto também. Portanto, a sua variou pouco, mas mais de uma hora.) Então, usei um executável nativo Win32, e fez a mesma tarefa, apenas despejar o arquivo caminho, modificados e tamanho para o console, e executado que a partir de Java. A velocidade foi incrível. O processo nativo, e minha cadeia de manipulação de ler os dados poderiam processar mais de 1000 itens por segundo.

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

A questão também me parece ser o Windows específico. OS X não teve o mesmo problema e poderia acessar a rede arquivo de informações tão rápido quanto o OS poderia fazê-lo.

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

- Ben

Se quiser que o tamanho do arquivo de múltiplos arquivos em um diretório, use Files.walkFileTree . Você pode obter o tamanho do BasicFileAttributes que você vai receber.

Isto é muito mais rápido, em seguida, chamando .length() sobre o resultado da File.listFiles() ou usando Files.size() sobre o resultado da Files.newDirectoryStream(). Em meus casos de teste que foi cerca de 100 vezes mais rápido.

Na verdade, eu acho que os "ls" pode ser mais rápido. Há definitivamente alguns problemas no Java que lidam com a obtenção de informações de arquivo. Infelizmente não há nenhum método seguro equivalente a ls recursiva para Windows. (Do cmd.exe DIR / S pode ficar confuso e gerar erros em loops infinitos)

No XP, o acesso a um servidor na LAN, ele me leva 5 segundos no Windows para obter a contagem dos arquivos em uma pasta (33.000), eo tamanho total.

Quando eu iteração recursiva através deste em Java, leva-me ao longo de 5 minutos. I começou a medir o tempo que leva para fazer file.length (), file.lastModified () e file.toURI () eo que eu encontrei é que 99% do meu tempo é tomado por esses 3 chamadas. Os 3 chamadas que realmente precisa fazer ...

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

Como um teste mais completo, eu usei WineMerge no XP para comparar a data de modificação e tamanho dos arquivos no servidor contra os arquivos localmente. Este foi repetindo ao longo de toda a árvore de diretórios de 33.000 arquivos em cada pasta. O tempo total, 7 segundos. java:. mais de 5 minutos

Assim, a declaração original e pergunta do OP é verdade, e válido. Sua menos perceptível quando se lida com um sistema de arquivos local. Fazendo um local de comparação da pasta com 33.000 itens leva 3 segundos em WinMerge, e leva 32 segundos localmente em Java. Então, novamente, java contra nativa é uma desaceleração 10x nestes testes rudimentares.

Java 1.6.0_22 (mais recente), Gigabit LAN e conexões de rede, ping for inferior a 1ms (ambos no mesmo switch)

Java é lento.

De referência do Ghad, há uma questão de poucas pessoas têm mencionado:

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

Porque disponível () retorna um estimativa do número de bytes que pode ser lida (ou pulados) a partir deste fluxo de entrada sem bloquear pelo seguinte invocação de um método para este fluxo de entrada.

1º Então, para remover a URL desta abordagem.

2> Como StuartH mencionado - a ordem do teste executado também fazer a diferença cache, de modo que ter por executar o teste separadamente

.

Agora iniciar o teste:

Quando um canal de corrida sozinho:

CHANNEL sum: 59691, per Iteration: 238.764

Quando o comprimento de um correr sozinho:

LENGTH sum: 48268, per Iteration: 193.072

Assim parece com o comprimento é o vencedor aqui:

@Override
public long getResult() throws Exception {
    File me = new File(FileSizeBench.class.getResource(
            "FileSizeBench.class").getFile());
    return me.length();
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top