1. Percorrer arquivos em um diretório
Obter a lista de arquivos: Files.list e Files.walk
No Java, há dois métodos muito úteis para trabalhar com o conteúdo de uma pasta:
- Files.list(Path) — retorna um stream (Stream<Path>) de todos os arquivos e pastas do nível superior no diretório especificado.
- Files.walk(Path) — retorna um stream de todos os arquivos e pastas no diretório e em todas as suas subpastas (recursivamente).
Exemplo: imprimir todos os arquivos e pastas no diretório data
import java.nio.file.*;
import java.io.IOException;
public class ListFilesExample {
public static void main(String[] args) throws IOException {
Path dir = Paths.get("data");
// Obtemos um stream de todos os arquivos e pastas do nível superior
try (var stream = Files.list(dir)) {
stream.forEach(System.out::println);
}
}
}
Se você quiser percorrer todas as subpastas (recursivamente), use Files.walk:
try (var stream = Files.walk(dir)) {
stream.forEach(System.out::println);
}
Atenção:
Files.walk pode retornar arquivos demais se a estrutura for grande — não execute isso na raiz do disco se não quiser ver “a lista de todo o universo”!
Visualização: diferença entre list e walk
| Método | O que retorna |
|---|---|
|
Apenas o conteúdo da pasta atual |
|
Todos os arquivos e pastas, incluindo os aninhados |
2. Filtragem de arquivos
Uma das maiores vantagens de trabalhar com streams (Stream<Path>) é a capacidade de filtrar arquivos por qualquer critério: extensão, nome, tamanho, data de criação etc.
Filtragem por extensão
Suponha que precisamos apenas de arquivos .txt. Podemos filtrá-los assim:
try (var stream = Files.list(dir)) {
stream
.filter(path -> path.toString().endsWith(".txt"))
.forEach(System.out::println);
}
Filtragem por nome
Queremos todos os arquivos cujo nome começa com "report":
stream
.filter(path -> path.getFileName().toString().startsWith("report"))
.forEach(System.out::println);
Filtragem por tamanho
Obter apenas arquivos “pesados” (maiores que 1 MB):
stream
.filter(path -> {
try {
return Files.size(path) > 1_000_000;
} catch (IOException e) {
return false;
}
})
.forEach(System.out::println);
Filtragem por data
Por exemplo, arquivos modificados nos últimos 7 dias:
import java.time.*;
import java.nio.file.attribute.*;
stream
.filter(path -> {
try {
FileTime lastModified = Files.getLastModifiedTime(path);
return lastModified.toInstant().isAfter(Instant.now().minus(Duration.ofDays(7)));
} catch (IOException e) {
return false;
}
})
.forEach(System.out::println);
Tudo junto: filtragem combinada
É possível combinar filtros “como em um restaurante”: por exemplo, todos os arquivos .log mais antigos que um mês e maiores que 10 KB:
stream
.filter(path -> path.toString().endsWith(".log"))
.filter(path -> {
try {
return Files.size(path) > 10_000;
} catch (IOException e) {
return false;
}
})
.filter(path -> {
try {
FileTime lastModified = Files.getLastModifiedTime(path);
return lastModified.toInstant().isBefore(Instant.now().minus(Duration.ofDays(30)));
} catch (IOException e) {
return false;
}
})
.forEach(System.out::println);
3. Cópia e exclusão em massa de arquivos
Operações em massa são simples: obtemos um stream de arquivos e, em seguida, aplicamos a função necessária a cada elemento. O principal é não esquecer os possíveis erros (por exemplo, o arquivo está em uso ou já existe).
Copiar todos os arquivos de uma pasta para outra
import java.nio.file.StandardCopyOption;
Path sourceDir = Paths.get("data");
Path targetDir = Paths.get("backup");
try (var stream = Files.list(sourceDir)) {
stream.forEach(path -> {
Path targetPath = targetDir.resolve(path.getFileName());
try {
Files.copy(path, targetPath, StandardCopyOption.REPLACE_EXISTING);
System.out.println("Copiado: " + path.getFileName());
} catch (IOException e) {
System.out.println("Erro ao copiar " + path.getFileName() + ": " + e.getMessage());
}
});
}
Excluir todos os arquivos temporários (*.tmp)
try (var stream = Files.list(dir)) {
stream
.filter(path -> path.toString().endsWith(".tmp"))
.forEach(path -> {
try {
Files.delete(path);
System.out.println("Excluído: " + path.getFileName());
} catch (IOException e) {
System.out.println("Erro ao excluir " + path.getFileName() + ": " + e.getMessage());
}
});
}
Uso da Stream API para processar coleções de caminhos
try (var stream = Files.list(dir)) {
stream
.filter(path -> path.toString().endsWith(".txt"))
.sorted((p1, p2) -> {
try {
return Long.compare(Files.size(p2), Files.size(p1)); // por tamanho, decrescente
} catch (IOException e) {
return 0;
}
})
.limit(5) // apenas os 5 maiores
.forEach(System.out::println);
}
4. Tarefas práticas
Localizar e excluir arquivos mais antigos que uma determinada data
Vamos excluir todos os arquivos que não foram modificados há mais de um ano:
try (var stream = Files.list(dir)) {
stream
.filter(path -> {
try {
FileTime lastModified = Files.getLastModifiedTime(path);
return lastModified.toInstant().isBefore(Instant.now().minus(Duration.ofDays(365)));
} catch (IOException e) {
return false;
}
})
.forEach(path -> {
try {
Files.delete(path);
System.out.println("Excluído: " + path.getFileName());
} catch (IOException e) {
System.out.println("Erro ao excluir " + path.getFileName() + ": " + e.getMessage());
}
});
}
Renomear arquivos em massa seguindo um padrão
Suponha que seja necessário adicionar o prefixo .txt "old_" a cada arquivo:
try (var stream = Files.list(dir)) {
stream
.filter(path -> path.toString().endsWith(".txt"))
.forEach(path -> {
Path newPath = path.resolveSibling("old_" + path.getFileName());
try {
Files.move(path, newPath);
System.out.println("Renomeado: " + path.getFileName() + " -> " + newPath.getFileName());
} catch (IOException e) {
System.out.println("Erro ao renomear " + path.getFileName() + ": " + e.getMessage());
}
});
}
Copiar todos os arquivos de todas as subpastas (recursivamente)
Usamos Files.walk para percorrer todos os arquivos em todas as subpastas:
try (var stream = Files.walk(sourceDir)) {
stream
.filter(Files::isRegularFile)
.forEach(path -> {
Path relative = sourceDir.relativize(path);
Path targetPath = targetDir.resolve(relative);
try {
Files.createDirectories(targetPath.getParent());
Files.copy(path, targetPath, StandardCopyOption.REPLACE_EXISTING);
System.out.println("Copiado: " + path + " -> " + targetPath);
} catch (IOException e) {
System.out.println("Erro ao copiar " + path + ": " + e.getMessage());
}
});
}
5. Pontos importantes e nuances
Tratamento de erros em operações em massa
Em operações em massa quase sempre há arquivos “problemáticos”: sem acesso, arquivo em uso, caminho muito longo, arquivo já existente etc. Se você não tratar as exceções dentro do loop, o programa pode “cair” no primeiro erro. Portanto, sempre use try-catch dentro de forEach para “engolir” o erro de um arquivo e ainda assim processar os demais.
Impacto da recursão no desempenho e na pilha de chamadas
Ao usar Files.walk em diretórios grandes, é possível obter um percurso muito longo se você fizer a recursão manualmente. Felizmente, o próprio método é implementado de forma eficiente e não causa estouro de pilha. Mas, se você escrever sua própria função recursiva para excluir/copiar diretórios, tenha cuidado: para estruturas com aninhamento enorme, a pilha pode “estourar”.
Trabalhando com pastas grandes
- Use Files.walk com cautela: se a pasta tiver milhares de arquivos, isso pode levar tempo e consumir muita memória.
- Se precisar processar apenas o nível superior — use Files.list.
- Para procurar arquivos por máscara, use filtragem por nome (endsWith, matches etc.).
Exemplo: “Limpar a pasta de arquivos temporários”
Path tempDir = Paths.get("data/tmp");
try (var stream = Files.walk(tempDir)) {
stream
.filter(Files::isRegularFile)
.filter(path -> path.toString().endsWith(".tmp"))
.forEach(path -> {
try {
Files.delete(path);
System.out.println("Excluído: " + path);
} catch (IOException e) {
System.out.println("Erro ao excluir " + path + ": " + e.getMessage());
}
});
}
Principais métodos para operações em massa
| Operação | Método/abordagem | Observações |
|---|---|---|
| Obter todos os arquivos | |
walk — recursivo; list — apenas o nível superior |
| Filtragem por extensão | |
Pode ser combinado com outros filtros |
| Cópia de um grupo de arquivos | |
Verifique a existência da pasta de destino |
| Exclusão de um grupo de arquivos | |
É melhor usar try-catch para cada operação |
| Renomeação em massa | |
Renomear é um move com um novo nome |
6. Erros comuns em operações em massa
Erro nº 1: Exceções não tratadas dentro de forEach.
Um problema muito comum: se você não envolver cada operação em try-catch, o programa vai “cair” no primeiro arquivo problemático e os demais não serão processados.
Erro nº 2: Tentar excluir/copiar uma pasta como se fosse arquivo (ou vice-versa).
Os métodos Files.delete e Files.copy trabalham de forma diferente com arquivos e pastas. Não os confunda! Por exemplo, tentar excluir uma pasta não vazia com o método padrão causará erro.
Erro nº 3: Formação incorreta do caminho de destino ao copiar.
Se você formar o caminho de destino sem levar em conta a estrutura (por exemplo, não usar relativize), pode sobrescrever arquivos ou obter uma estrutura errada no arquivo final.
Erro nº 4: Abrir um número muito grande de arquivos simultaneamente.
Se você abrir streams de leitura/gravação dentro do loop, não se esqueça de fechá-los! É melhor usar try-with-resources.
Erro nº 5: Percurso recursivo sem limitar a profundidade.
Ao usar Files.walk em pastas muito grandes e profundas, você pode ter problemas de desempenho e memória. Se precisar limitar a profundidade — use a sobrecarga com parâmetro de profundidade: Files.walk(dir, depth).
Erro nº 6: Ignorar implicitamente arquivos ocultos/sistêmicos.
Se o aplicativo trabalha com arquivos do usuário, você pode tocar acidentalmente em arquivos ocultos ou de sistema. Para filtrá-los, use o método Files.isHidden(path).
GO TO FULL VERSION