CodeGym /Cursos /JAVA 25 SELF /Operações em massa com arquivos

Operações em massa com arquivos

JAVA 25 SELF
Nível 40 , Lição 2
Disponível

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
Files.list
Apenas o conteúdo da pasta atual
Files.walk
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
Files.list, Files.walk
walk — recursivo; list — apenas o nível superior
Filtragem por extensão
filter(path -> ...)
Pode ser combinado com outros filtros
Cópia de um grupo de arquivos
forEach + Files.copy
Verifique a existência da pasta de destino
Exclusão de um grupo de arquivos
forEach + Files.delete
É melhor usar try-catch para cada operação
Renomeação em massa
forEach + Files.move
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).

1
Tarefa
JAVA 25 SELF, nível 40, lição 2
Bloqueado
Criação de backup de dados críticos 💾
Criação de backup de dados críticos 💾
1
Tarefa
JAVA 25 SELF, nível 40, lição 2
Bloqueado
Bibliotecário digital: coleta de tesouros textuais 📚
Bibliotecário digital: coleta de tesouros textuais 📚
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION