[Resolvido] nio Files.size não retorna tamanho correto

7 respostas
juno.rr

Olá pessoal.

Criei uma classe para calcular o tamanho total dos arquivos de um diretório, utilizando java.nio.file.Files#walkFileTree(…).
O que acontece é que o método Files.size( Path ) retorna sempre o mesmo resultado para qualquer arquivo, sempre 4096 bytes.
Li que a implementação depende do sistema operacional e do sistema de arquivos e o resultado dependo do arquivo. Neste caso são arquivos de banco de dados access e arquivos de textos, mas para qualquer um o resultado é o tamanho do bloco do sistema de arquivos e não o tamanho do arquivo em si. Alguém sabe o porque disto e como contornar?

7 Respostas

E

Bom, deixa eu escrever um método desses para testar.

Mas imagino que você esteja chamando Files.size sobre um diretório; como você deve saber, um diretório também é um arquivo (se você estiver usando Unix/Linux, você consegue saber até o tamanho do arquivo que armazena as entradas do diretório. Esse arquivo começa com o tamanho igual ao de um cluster, e depois vai subindo - se você criar um diretório com muitas entradas e depois deletar todas, provavelmente o tamanho não se alterará.

No caso do Windows, esse tamanho não é reportado para a aplicação e aparece como zero (mesmo para uma aplicaçao Cygwin).

juno.rr

Não é sobre um diretório, é sobre os arquivos mesmo. Vou colocar o código:

public class SizeFileVisitor implements FileVisitor<Path> {
  private Path path;
  private long size;
  
  public SizeFileVisitor(String path) {
    if(path == null || path.trim().isEmpty())
      throw new IllegalArgumentException(
          "Invalid path: "+ path);
    size = 0;
    this.path = Paths.get(path);
    if(!Files.exists(this.path))
      throw new IllegalArgumentException(
          "path does not exists: "+ path);
  }
  
  public SizeFileVisitor(Path path) {
    if(path == null || !Files.exists(path))
      throw new IllegalArgumentException(
          "Invalid path: "+ path);
    size = 0;
    this.path = path;
  }
  
  public long calculateSize() {
    size = 0;
    try {
      Files.walkFileTree(path, this);
      return size;
    } catch(IOException ex) {
      throw new RuntimeException(ex);
    }
  }
  
  @Override
  public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
    return FileVisitResult.CONTINUE;
  }

  @Override
  public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
    size += Files.size(file);
    return FileVisitResult.CONTINUE;
  }

  @Override
  public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
    return FileVisitResult.TERMINATE;
  }

  @Override
  public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
    return FileVisitResult.CONTINUE;
  }

  public static void main(String[] args) {
    SizeFileVisitor sf = new SizeFileVisitor("D:/zzz/src");
    System.out.println("* calculating size (D:/zzz/src) ...");
    System.out.println("* size = "+ sf.calculateSize());
  }
  
}

Como da pra ver na linha 41, o Files.size( Path ) é chamado para cada arquivo que é encontrado.

gomesrod

Colei sua classe aqui exatamente igual e funcionou.

Só fiz uma alteração que foi colocar um print no início do método visitFile()

System.out.println(file.toUri() + "    " + Files.size(file));

E ele mostrou o tamanho de cada arquivo em bytes, conferi alguns pelas propriedades do arquivo no Windows e está certinho.
O total do diretório também bate com o que o Windows informa.

E

Rodei o programa abaixo em uma máquina Windows. De fato, para alguns diretórios, o tamanho reportado é zero, para outros é 4096, 8192 etc.

De qualquer forma, esse valor é na prática quase inútil para você. Ele é o “tamanho do diretório” mas não é o tamanho correto, já que para diretórios com algumas poucas entradas ele reporta como zero em vez de 4096.

package guj;
import java.util.*;
import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.*;

public class ListarDiretorioComJavaNioFile {

    /**
     * @param args
     */
    public static void main(String[] args) throws Exception {
        Path start = FileSystems.getDefault().getPath (args[0]);
        Set<FileVisitOption> options = new TreeSet<FileVisitOption>();
        
        Files.walkFileTree (start, options, Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile (Path file, BasicFileAttributes attrs) throws IOException {
                // O tamanho de um arquivo é realmente o tamanho de um arquivo
                assert (attrs.size() == Files.size(file));
                return FileVisitResult.CONTINUE;
            }
            @Override
            public FileVisitResult postVisitDirectory (Path dir, IOException e) throws IOException {
                if (e == null) {
                    // Files.size() para um diretório, no caso do Windows, é uma estimativa inferior
                    // do tamanho do arquivo que representa o diretório, 
                    // não dos arquivos contidos dentro desse diretório
                    System.out.printf ("%,7d : %s%n", Files.size (dir), dir.toString());
                    return FileVisitResult.CONTINUE;
                } else {
                    e.printStackTrace();
                    return FileVisitResult.CONTINUE;
                }
            }
        });
    }

}
E

Bom, então vamos lá. O jeito mais bobo, então, de achar uma soma recursiva dos tamanhos dos arquivos do diretório e subdiretórios, vai abaixo.

Na prática, provavelmente você injetaria algum “listener” para poder atualizar, por exemplo, um JLabel, para que a espera não seja excessiva por parte do usuário.

package guj;
import java.util.*;
import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.*;

public class ListarDiretorioComJavaNioFile {
    static class DirectorySize extends SimpleFileVisitor<Path> {
        private long totalSize;
        public DirectorySize() {
            totalSize = 0;
        }
        public long getDirectorySize(Path start) throws IOException {
            totalSize = 0;
            Files.walkFileTree (start, new HashSet<FileVisitOption>(), Integer.MAX_VALUE, this);
            return totalSize;
        }
        @Override
        public FileVisitResult visitFile (Path file, BasicFileAttributes attrs) throws IOException {
            totalSize += attrs.size();
            return FileVisitResult.CONTINUE;
        }
    }
    
    /**
     * @param args
     */
    public static void main(String[] args) throws Exception {
        Path start = FileSystems.getDefault().getPath (args[0]);
        DirectorySize ds = new DirectorySize();
        
        System.out.printf ("%,7d : %s%n", ds.getDirectorySize(start), start.toString());
    }

}
E

Note que eu usei “attrs.size()” em vez de “Files.size (file)” embora em tese devesse dar o mesmo resultado.

Acredito que possam dar resultados diferentes se você não tiver acesso ao arquivo, apenas ao diretório (isso não é incomum no Unix, talvez seja possível fazer no Windows com algum esforço).

juno.rr

Legal pessoal, valeu pela força.
entanglement, atualizei meu código para como está o seu e funcionou, mas não faz muito sentido isso…
Pensei em uma teoria: O diretório que estava fazendo o teste era o drive D:, uma partição separada formatada em ntfs. Havia colado aquela pasta (D:\zzz\src) para testes. Depois de alterar o código, funcionou. Acredito q o windows não havia gravado os dados de fato ainda, por isso retornava sempre 4096 para qualquer arquivo.
Será q isso faz sentido?

Valeu abraço.

Criado 2 de maio de 2013
Ultima resposta 2 de mai. de 2013
Respostas 7
Participantes 3