CodeGym /Cours /JAVA 25 SELF /Opérations de masse sur les fichiers

Opérations de masse sur les fichiers

JAVA 25 SELF
Niveau 40 , Leçon 2
Disponible

1. Itération des fichiers dans un répertoire

Obtention de la liste des fichiers : Files.list et Files.walk

En Java, pour travailler avec le contenu d’un dossier, il existe deux méthodes très utiles :

  • Files.list(Path) — renvoie un flux (Stream<Path>) de tous les fichiers et dossiers de premier niveau dans le répertoire donné.
  • Files.walk(Path) — renvoie un flux de tous les fichiers et dossiers dans le répertoire et dans tous ses sous-dossiers (récursivement).

Exemple : affichage de tous les fichiers et dossiers du répertoire 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");

        // Nous obtenons le flux de tous les fichiers et dossiers du niveau supérieur
        try (var stream = Files.list(dir)) {
            stream.forEach(System.out::println);
        }
    }
}

Si vous souhaitez parcourir tous les sous-dossiers (récursivement), utilisez Files.walk :

try (var stream = Files.walk(dir)) {
    stream.forEach(System.out::println);
}

Attention :
Files.walk peut renvoyer énormément de fichiers si la structure est vaste — ne lancez pas cela à la racine du disque si vous ne voulez pas voir « la liste de tout l’univers » !

Visualisation : différence entre list et walk

Méthode Ce qu’elle renvoie
Files.list
Uniquement le contenu du dossier courant
Files.walk
Tous les fichiers et dossiers, y compris ceux imbriqués

2. Filtrage des fichiers

L’un des principaux avantages des flux (Stream<Path>) est la possibilité de filtrer les fichiers selon n’importe quel critère : extension, nom, taille, date de création, etc.

Filtrage par extension

Supposons que nous ne voulons que les fichiers .txt. On peut les filtrer ainsi :

try (var stream = Files.list(dir)) {
    stream
        .filter(path -> path.toString().endsWith(".txt"))
        .forEach(System.out::println);
}

Filtrage par nom

Nous voulons tous les fichiers dont le nom commence par "report" :

stream
    .filter(path -> path.getFileName().toString().startsWith("report"))
    .forEach(System.out::println);

Filtrage par taille

Récupérons uniquement les fichiers « lourds » (plus de 1 Mo) :

stream
    .filter(path -> {
        try {
            return Files.size(path) > 1_000_000;
        } catch (IOException e) {
            return false;
        }
    })
    .forEach(System.out::println);

Filtrage par date

Par exemple, les fichiers modifiés au cours des 7 derniers jours :

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);

Tout ensemble : filtrage combiné

On peut combiner les filtres « comme au restaurant » : par exemple, tous les fichiers .log plus vieux qu’un mois et de plus de 10 Ko :

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. Copie et suppression en masse de fichiers

Les opérations de masse, c’est simple : on obtient un flux de fichiers, puis on applique la fonction voulue à chaque élément. L’essentiel est de ne pas oublier les erreurs possibles (par exemple, fichier en cours d’utilisation ou déjà existant).

Copier tous les fichiers d’un dossier vers un autre

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("Copié : " + path.getFileName());
        } catch (IOException e) {
            System.out.println("Erreur de copie " + path.getFileName() + " : " + e.getMessage());
        }
    });
}

Suppression de tous les fichiers temporaires (*.tmp)

try (var stream = Files.list(dir)) {
    stream
        .filter(path -> path.toString().endsWith(".tmp"))
        .forEach(path -> {
            try {
                Files.delete(path);
                System.out.println("Supprimé : " + path.getFileName());
            } catch (IOException e) {
                System.out.println("Erreur de suppression " + path.getFileName() + " : " + e.getMessage());
            }
        });
}

Utiliser l’API Stream pour traiter des collections de chemins

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)); // par taille, ordre décroissant
            } catch (IOException e) {
                return 0;
            }
        })
        .limit(5) // seulement les 5 plus gros
        .forEach(System.out::println);
}

4. Exercices pratiques

Recherche et suppression des fichiers antérieurs à une certaine date

Supprimons tous les fichiers qui n’ont pas été modifiés depuis plus d’un an :

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("Supprimé : " + path.getFileName());
            } catch (IOException e) {
                System.out.println("Erreur de suppression " + path.getFileName() + " : " + e.getMessage());
            }
        });
}

Renommage en masse des fichiers selon un modèle

Supposons qu’il faille ajouter à chaque fichier .txt le préfixe "old_" :

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("Renommé : " + path.getFileName() + " -> " + newPath.getFileName());
            } catch (IOException e) {
                System.out.println("Erreur de renommage " + path.getFileName() + " : " + e.getMessage());
            }
        });
}

Copie de tous les fichiers de tous les sous-dossiers (récursivement)

Utilisons Files.walk pour parcourir tous les fichiers dans tous les sous-dossiers :

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("Copié : " + path + " -> " + targetPath);
            } catch (IOException e) {
                System.out.println("Erreur de copie " + path + " : " + e.getMessage());
            }
        });
}

5. Points importants et subtilités

Gestion des erreurs lors des opérations de masse

Dans les opérations de masse, on rencontre presque toujours des fichiers « problématiques » : pas d’accès, fichier occupé, chemin trop long, fichier déjà existant, etc. Si vous ne gérez pas les exceptions à l’intérieur de la boucle, le programme peut « tomber » dès la première erreur. Utilisez donc toujours try-catch à l’intérieur de forEach pour « avaler » l’erreur d’un fichier mais traiter les autres.

Impact de la récursivité sur les performances et la pile d’appels

Avec Files.walk sur de grands répertoires, on peut obtenir un parcours très long si l’on fait la récursivité manuellement. Heureusement, la méthode elle-même est implémentée efficacement et n’entraîne pas de dépassement de pile. Mais si vous écrivez votre propre fonction récursive pour supprimer/copier des répertoires, soyez prudent : pour des structures extrêmement imbriquées, la pile peut « exploser ».

Travailler avec de grands dossiers

  • Utilisez Files.walk avec prudence : si un dossier contient des milliers de fichiers, cela peut prendre du temps et « consommer » beaucoup de mémoire.
  • Si vous ne devez traiter que le niveau supérieur — utilisez Files.list.
  • Pour rechercher des fichiers par motif, utilisez le filtrage par nom (endsWith, matches, etc.).

Exemple : « Nettoyer un dossier des fichiers temporaires »

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("Supprimé : " + path);
            } catch (IOException e) {
                System.out.println("Erreur de suppression " + path + " : " + e.getMessage());
            }
        });
}

Méthodes principales pour les opérations de masse

Opération Méthode/approche Particularités
Obtenir tous les fichiers
Files.list, Files.walk
walk — récursif, list — uniquement le niveau supérieur
Filtrage par extension
filter(path -> ...)
Peut se combiner avec d’autres filtres
Copie d’un groupe de fichiers
forEach + Files.copy
Vérifiez l’existence du dossier cible
Suppression d’un groupe de fichiers
forEach + Files.delete
Mieux vaut utiliser try-catch pour chaque opération
Renommage en masse
forEach + Files.move
Renommer — c’est un move avec un nouveau nom

6. Erreurs courantes lors des opérations de masse

Erreur n° 1 : exceptions non gérées dans forEach.
Problème très courant : si vous n’encapsulez pas chaque opération dans try-catch, le programme « tombera » sur le premier fichier problématique et les autres ne seront pas traités.

Erreur n° 2 : tentative de supprimer/copier un dossier comme un fichier (ou inversement).
Les méthodes Files.delete et Files.copy se comportent différemment avec les fichiers et les dossiers. Ne les confondez pas ! Par exemple, tenter de supprimer un dossier non vide avec la méthode standard provoquera une erreur.

Erreur n° 3 : mauvaise construction du chemin cible lors de la copie.
Si vous construisez le chemin de destination sans tenir compte de la structure (par exemple, sans utiliser relativize), vous pouvez écraser des fichiers ou obtenir une structure incorrecte dans l’archive.

Erreur n° 4 : ouverture d’un trop grand nombre de fichiers simultanément.
Si vous ouvrez des flux de lecture/écriture dans une boucle, n’oubliez pas de les fermer ! Il vaut mieux utiliser try-with-resources.

Erreur n° 5 : parcours récursif sans limite de profondeur.
Avec Files.walk sur des dossiers très grands et profonds, vous pouvez rencontrer des problèmes de performances et de mémoire. Si vous devez limiter la profondeur — utilisez la surcharge avec un paramètre de profondeur : Files.walk(dir, depth).

Erreur n° 6 : ignorer implicitement des fichiers cachés/système.
Si votre application manipule des fichiers utilisateur, vous pouvez toucher par inadvertance des fichiers cachés ou système. Pour les filtrer, utilisez la méthode Files.isHidden(path).

1
Mission
JAVA 25 SELF, niveau 40, leçon 2
Bloqué
Création d'une sauvegarde des données critiques 💾
Création d'une sauvegarde des données critiques 💾
1
Mission
JAVA 25 SELF, niveau 40, leçon 2
Bloqué
Bibliothécaire numérique : collecte des trésors textuels 📚
Bibliothécaire numérique : collecte des trésors textuels 📚
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION