Files.walk (), calcule el tamaño total

Estoy tratando de calcular el tamaño de los archivos en mi disco. En java-7 esto podría hacerse usando Files.walkFileTree como se muestra en mi respuesta aquí .

Sin embargo, si quería hacer esto usando las transmisiones java-8 funcionaría para algunas carpetas, pero no para todas.

public static void main(String[] args) throws IOException { long size = Files.walk(Paths.get("c:/")).mapToLong(MyMain::count).sum(); System.out.println("size=" + size); } static long count(Path path) { try { return Files.size(path); } catch (IOException | UncheckedIOException e) { return 0; } } 

El código anterior funcionará bien para la ruta a:/files/ pero para c:/ arrojará debajo de la excepción

 Exception in thread "main" java.io.UncheckedIOException: java.nio.file.AccessDeniedException: c:\$Recycle.Bin\S-1-5-20 at java.nio.file.FileTreeIterator.fetchNextIfNeeded(Unknown Source) at java.nio.file.FileTreeIterator.hasNext(Unknown Source) at java.util.Iterator.forEachRemaining(Unknown Source) at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Unknown Source) at java.util.stream.AbstractPipeline.copyInto(Unknown Source) at java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source) at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(Unknown Source) at java.util.stream.AbstractPipeline.evaluate(Unknown Source) at java.util.stream.LongPipeline.reduce(Unknown Source) at java.util.stream.LongPipeline.sum(Unknown Source) at MyMain.main(MyMain.java:16) 

Entiendo de dónde viene y cómo evitarlo usando la API Files.walkFileTree.

Pero, ¿cómo se puede evitar esta excepción al usar la API Files.walk () ?

No, esta excepción no se puede evitar.

La excepción en sí ocurre dentro de la recuperación perezosa de Files.walk() , por lo tanto, por qué no lo ves temprano y por qué no hay forma de eludirlo, considera el siguiente código:

 long size = Files.walk(Paths.get("C://")) .peek(System.out::println) .mapToLong(this::count) .sum(); 

En mi sistema esto se imprimirá en mi computadora:

 C:\ C:\$Recycle.Bin Exception in thread "main" java.io.UncheckedIOException: java.nio.file.AccessDeniedException: C:\$Recycle.Bin\S-1-5-18 

Y como una excepción se lanza en el hilo (principal) en el tercer archivo, todas las ejecuciones posteriores en ese hilo se detienen.

Creo que esto es una falla de diseño, porque tal como está ahora Files.walk es absolutamente inutilizable, porque nunca se puede garantizar que no haya errores al caminar sobre un directorio.

Un punto importante a tener en cuenta es que stacktrace incluye una operación de sum() y de reduce() , esto se debe a que la ruta se carga de forma lenta, por lo que en el punto de reduce() , se llama la mayor parte de la maquinaria (visible en stacktrace ), y luego busca la ruta, en cuyo punto se produce la UnCheckedIOException .

Posiblemente se podría eludir si permites que cada operación de caminar se ejecute en su propio hilo. Pero eso no es algo que quisieras hacer de todos modos.

Además, verificar si un archivo es realmente accesible no tiene valor (aunque es útil hasta cierto punto), ya que no se puede garantizar que sea legible incluso 1 ms más tarde.

Extensión futura

Creo que todavía se puede arreglar, aunque no sé cómo FileVisitOption exactamente FileVisitOption .
Actualmente hay un FileVisitOption.FOLLOW_LINKS , si opera por archivo, entonces sospecho que también se puede agregar un FileVisitOption.IGNORE_ON_IOEXCEPTION , pero no podemos FileVisitOption.IGNORE_ON_IOEXCEPTION correctamente esa funcionalidad allí.

2017 para aquellos que siguen llegando aquí.

Use Files.walk () cuando esté seguro del comportamiento del sistema de archivos y realmente quiera detenerlo cuando haya algún error. En general, Files.walk no es útil en aplicaciones independientes. Cometí este error tan a menudo, tal vez soy flojo. Me doy cuenta de mi error en el momento en el que veo que el tiempo que lleva transcurrir más de unos pocos segundos para algo tan pequeño como 1 millón de archivos.

Recomiendo walkFileTree . Comience implementando la interfaz FileVisitor, aquí solo quiero contar los archivos. Mal nombre de clase, lo sé.

 class Recurse implements FileVisitor{ private long filesCount; @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { //This is where I need my logic filesCount++; return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { // This is important to note. Test this behaviour return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } public long getFilesCount() { return filesCount; } 

}

Luego usa tu clase definida así.

 Recurse r = new Recurse(); Files.walkFileTree(Paths.get("G:"), r); System.out.println("Total files: " + r.getFilesCount()); 

Estoy seguro de que sabes cómo modificar la implementación de tu propia clase de la FileVisitor Interface para hacer otras cosas como filesize con el ejemplo que publiqué. Consulte los documentos para otros métodos en este

Velocidad:

  • Files.walk: más de 20 minutos y falla con excepción
  • Files.walkFileTree: 5.6 segundos, hecho con una respuesta perfecta.

Editar: al igual que con todo, use pruebas para confirmar el comportamiento. Controle las excepciones, todavía ocurren, excepto las que elegimos que no nos importen como se indica anteriormente.

La respuesta corta es que no puedes.

La excepción proviene de FileTreeWalker.visit .

Para ser precisos, está intentando construir un newDirectoryStream cuando falla (este código está fuera de tu control):

 // file is a directory, attempt to open it DirectoryStream stream = null; try { stream = Files.newDirectoryStream(entry); } catch (IOException ioe) { return new Event(EventType.ENTRY, entry, ioe); // ==> Culprit <== } catch (SecurityException se) { if (ignoreSecurityException) return null; throw se; } 

Tal vez deberías enviar un error .

Descubrí que usar la clase de Archivos de Guava resolvió el problema para mí:

  Iterable files = Files.fileTreeTraverser().breadthFirstTraversal(dir); long size = toStream( files ).mapToLong( File::length ).sum(); 

Donde toStream es mi función de utilidad estática para convertir un Iterable en un Stream. Sólo esta:

 StreamSupport.stream(iterable.spliterator(), false);