CodeGym /Cursos /JAVA 25 SELF /NIO2: Files, Paths, Files.walk: percorrendo o sistema de ...

NIO2: Files, Paths, Files.walk: percorrendo o sistema de arquivos

JAVA 25 SELF
Nível 39 , Lição 0
Disponível

1. NIO2: conhecendo melhor

Nós já tivemos contato com NIO2, mas agora vamos revisar e aprofundar o conhecimento sobre essa biblioteca útil para trabalhar com arquivos e diretórios.

Antes, no Java, havia apenas a classe File. Ela conseguia verificar a existência do arquivo, criar e excluir arquivos e pastas, obter a lista de arquivos em um diretório. Mas tinha muitas limitações:

  • trabalhar com caminhos era inconveniente, especialmente ao considerar diferentes sistemas operacionais (C:\Users\user\file.txt no Windows e /home/user/file.txt no Linux);
  • não havia suporte adequado a links simbólicos, permissões e atributos de arquivos;
  • as capacidades de percorrer a árvore de diretórios eram limitadas;
  • o tratamento de erros deixava a desejar.

Com o surgimento do NIO2 (New Input/Output, versão 2) no Java 7, a vida do desenvolvedor ficou mais simples e agradável. Agora temos à disposição:

  • a classe Path para trabalhar de forma conveniente com caminhos de arquivos e pastas;
  • a classe Files, que oferece as operações principais: leitura, escrita, cópia, exclusão, obtenção de informações sobre arquivos;
  • a interface FileVisitor e métodos como Files.walk, que permitem percorrer o sistema de arquivos de maneira fácil e flexível.

Por que isso é importante?

  • Portabilidade: O mesmo código funciona no Windows, Linux e macOS, sem se preocupar com separadores (/ ou \).
  • Segurança e praticidade: Mais informações sobre erros, menos mágica e menos surpresas inesperadas.
  • Potência: É possível processar diretórios enormes e até percorrer recursivamente com filtragem e processamento paralelo.

2. Classes principais: Path e Files

Classe Path

Path — é uma representação moderna de um caminho para arquivo ou pasta. Ele não precisa apontar para um arquivo realmente existente — é apenas um caminho com o qual é conveniente trabalhar.

Obtendo um Path

import java.nio.file.Path;
import java.nio.file.Paths;

Path path1 = Paths.get("file.txt"); // caminho relativo
Path path2 = Paths.get("/home/user/file.txt"); // caminho absoluto
Path path3 = Path.of("mydir", "subdir", "file.txt"); // desde o Java 11+

Fato: Path é independente do sistema operacional. Esqueça colar strings manualmente com / ou \!

Conversão para string

System.out.println(path1.toString());

Obtendo o diretório pai e o nome do arquivo

Path parent = path1.getParent(); // pode ser null para caminhos relativos
Path fileName = path1.getFileName(); // apenas o nome do arquivo

Classe Files

Files — é um conjunto de métodos estáticos para todas as operações com arquivos e diretórios:

  • Verificação de existência: Files.exists(path)
  • Leitura e escrita de arquivos: Files.readAllBytes(path), Files.write(path, bytes)
  • Obtenção de informações: Files.size(path), Files.getLastModifiedTime(path)
  • Cópia, exclusão, movimentação: Files.copy, Files.delete, Files.move

Exemplos:

import java.nio.file.Files;
import java.nio.file.Path;

Path path = Path.of("file.txt");
if (Files.exists(path)) {
    System.out.println("O arquivo existe!");
    System.out.println("Tamanho: " + Files.size(path) + " bytes");
    System.out.println("Última modificação: " + Files.getLastModifiedTime(path));
} else {
    System.out.println("Arquivo não encontrado.");
}

3. Percorrendo o sistema de arquivos: Files.walk e companhia

O problema do modo antigo

No API antigo, para percorrer todos os arquivos em uma pasta e suas subpastas, era preciso escrever funções recursivas, verificar manualmente onde era arquivo e onde era pasta e garantir que não haveria recursão infinita. Isso não só era cansativo, como também muito suscetível a erros.

Abordagem moderna: Files.walk

Files.walk(Path start) retorna um Stream<Path> — um fluxo de todos os arquivos e pastas a partir do caminho indicado, incluindo todos os subdiretórios. Agora percorrer o sistema de arquivos é simplesmente trabalhar com streams!

Exemplo: listar todos os arquivos e pastas

import java.nio.file.*;

try (var paths = Files.walk(Path.of("mydir"))) {
    paths.forEach(System.out::println);
}

Aqui serão impressos todos os caminhos: arquivos e pastas, a partir de mydir.

Exemplo: apenas arquivos (sem pastas)

try (var paths = Files.walk(Path.of("mydir"))) {
    paths.filter(Files::isRegularFile)
         .forEach(System.out::println);
}

O método Files.isRegularFile(path) retornará true apenas para arquivos normais (não pastas, não links simbólicos).

Exemplo: buscar arquivos por extensão

Suponha que precisamos encontrar todos os arquivos .txt no diretório e subdiretórios:

try (var paths = Files.walk(Path.of("mydir"))) {
    paths.filter(Files::isRegularFile)
         .filter(path -> path.toString().endsWith(".txt"))
         .forEach(System.out::println);
}

Exemplo: calcular o tamanho total de todos os arquivos

long totalSize = 0;
try (var paths = Files.walk(Path.of("mydir"))) {
    totalSize = paths.filter(Files::isRegularFile)
                     .mapToLong(path -> {
                         try {
                             return Files.size(path);
                         } catch (Exception e) {
                             System.err.println("Erro ao ler o tamanho: " + path);
                             return 0L;
                         }
                     })
                     .sum();
}
System.out.println("Tamanho total dos arquivos: " + totalSize + " bytes");

Importante!

  • O método Files.walk retorna um stream que precisa ser fechado (ele implementa AutoCloseable). Por isso, use try-with-resources!
  • Por padrão, a profundidade da varredura vai até o fim (todos os subdiretórios). É possível limitar a profundidade: Files.walk(path, maxDepth)

4. Tarefas práticas

Tarefa 1: encontrar todas as imagens no diretório

Precisamos encontrar todos os arquivos com extensão .jpg, .png, .gif na pasta images e imprimir seus nomes.

import java.nio.file.*;
import java.util.Set;

Set<String> extensions = Set.of(".jpg", ".png", ".gif");

try (var paths = Files.walk(Path.of("images"))) {
    paths.filter(Files::isRegularFile)
         .filter(path -> {
             String name = path.getFileName().toString().toLowerCase();
             return extensions.stream().anyMatch(name::endsWith);
         })
         .forEach(System.out::println);
}

Tarefa 2: copiar todos os arquivos .txt para outra pasta

import java.nio.file.*;

Path sourceDir = Path.of("src");
Path destDir = Path.of("dest");

try (var paths = Files.walk(sourceDir)) {
    paths.filter(Files::isRegularFile)
         .filter(path -> path.toString().endsWith(".txt"))
         .forEach(path -> {
             try {
                 Path relative = sourceDir.relativize(path);
                 Path target = destDir.resolve(relative);
                 Files.createDirectories(target.getParent());
                 Files.copy(path, target, StandardCopyOption.REPLACE_EXISTING);
                 System.out.println("Copiado: " + path + " -> " + target);
             } catch (Exception e) {
                 System.err.println("Erro de cópia: " + path);
             }
         });
}

Aqui preservamos a estrutura dos subdiretórios.

5. Dicas úteis

Vantagens do NIO2

Portabilidade entre plataformas

Path lida sozinho com os separadores de pastas. Seu código funcionará da mesma forma no Windows, Linux e macOS.

Processamento com streams

Métodos como Files.walk retornam um fluxo (Stream<Path>) que pode ser filtrado, transformado, coletado em coleções — tudo o que o Stream API oferece.

Trabalho com diretórios grandes

O API antigo podia falhar se houvesse arquivos demais (por exemplo, 100 000 fotos). NIO2 lida bem com esses casos, pois não carrega tudo na memória de uma vez.

Suporte a links simbólicos, atributos e permissões de acesso

É possível saber se um caminho é um link simbólico (Files.isSymbolicLink(path)), obter permissões (Files.getPosixFilePermissions(path)), descobrir o proprietário do arquivo e muito mais.

Comparação entre o API antigo e o novo

Operação API antigo (File) API novo (Path, Files)
Verificar existência
file.exists()
Files.exists(path)
Obter tamanho
file.length()
Files.size(path)
Listar arquivos na pasta
file.listFiles()
Files.list(path)
Percurso recursivo Recursão manual
Files.walk(path)
Copiar arquivo file.renameTo() (problemático)
Files.copy(src, dest)
Obter extensão Fazer parsing da string
path.getFileName().toString()
Obter o pai
file.getParentFile()
path.getParent()
Trabalhar com permissões Quase nada
Files.getPosixFilePermissions(path)

Características importantes

Verificação do tipo de arquivo

  • Files.isRegularFile(path) — arquivo normal
  • Files.isDirectory(path) — pasta
  • Files.isSymbolicLink(path) — link simbólico

Trabalhar com diretórios grandes

  • Evite juntar todos os caminhos em uma lista: trabalhe com streams (Stream<Path>) e processe conforme chegam.
  • Ao terminar o processamento, feche o stream obrigatoriamente (try-with-resources).

Exceções

  • Quase todos os métodos podem lançar IOException — não se esqueça de tratar os erros (ou propagá-los).

Limitar a profundidade da varredura

try (var paths = Files.walk(Path.of("mydir"), 2)) { // apenas 2 níveis
    // ...
}

6. Erros comuns ao trabalhar com NIO2

Erro nº 1: esquecer de fechar o stream do walk. Se não usar try-with-resources, você pode ter vazamento de recursos — o fluxo do sistema de arquivos permanecerá aberto. Sempre use a construção try (var paths = Files.walk(...)) { ... }.

Erro nº 2: não verificar que o caminho é um diretório. Se você passar para Files.walk um caminho para arquivo em vez de pasta, pode obter um comportamento inesperado ou um erro.

Erro nº 3: não tratar as exceções. Praticamente todos os métodos do NIO2 podem lançar IOException. Não ignore esses erros — ao menos mostre uma mensagem ao usuário ou registre no log.

Erro nº 4: confusão com separadores de caminho. Se você estiver colando caminhos manualmente com / ou \, não faça isso! Use Path.of(...) ou resolve(...) — eles cuidarão disso para você.

Erro nº 5: tentar carregar um diretório enorme “na memória”. Não junte todos os caminhos em uma lista quando houver muitos arquivos — trabalhe com streams (Stream<Path>) e processe-os conforme chegam.

Erro nº 6: esquecer da portabilidade entre plataformas. Não faça hardcode de caminhos absolutos com estilo de Windows ou Unix. Use Path e operações como resolve/relativize — elas farão o correto em qualquer sistema operacional.

1
Tarefa
JAVA 25 SELF, nível 39, lição 0
Bloqueado
Inventário digital: visão do conteúdo da pasta
Inventário digital: visão do conteúdo da pasta
1
Tarefa
JAVA 25 SELF, nível 39, lição 0
Bloqueado
Caça ao tesouro do desenvolvedor: encontre todos os arquivos Java
Caça ao tesouro do desenvolvedor: encontre todos os arquivos Java
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION