Lire des fichiers en Java
Lisez des fichiers texte et binaires en Java avec FileReader, BufferedReader, Scanner, Files.readString et les streams.
Il existe cinq façons courantes de lire un fichier texte en Java, et le bon choix dépend presque entièrement de la taille du fichier et de ce que vous souhaitez faire avec son contenu. Ce chapitre présente les cinq approches, de la plus simple à la plus flexible :
Files.readString(path)— fichier entier en tant queString.Files.readAllLines(path)— fichier entier en tant queList<String>.Files.readAllBytes(path)— fichier entier en tant quebyte[].Files.lines(path)— fichier en tant queStream<String>, en mode paresseux.BufferedReader/Scanner— décorateurs classiques, contrôle total.
Choisissez l'outil le plus simple qui convient. Lire un journal de 4 Go avec Files.readString provoque une OutOfMemoryError ; lire une configuration de 12 lignes avec BufferedReader et une boucle while nécessite six lignes de code là où une seule suffirait.
Files.readString(path) — fichier entier, en un seul appel
String text = Files.readString(Path.of("config.json"), StandardCharsets.UTF_8);Ajouté en Java 11. Retourne le fichier complet en tant que String. Utilise UTF-8 par défaut depuis Java 18 (Charset est toujours fortement recommandé à spécifier explicitement, même avec ce nouveau comportement par défaut). Lève une IOException si le fichier n'existe pas ou ne peut pas être lu ; lève une OutOfMemoryError si le fichier est plus grand que le tas.
À utiliser quand : le fichier est « suffisamment petit » — fichiers de configuration, charges utiles JSON, chapitres MDX, tout ce que vous accepteriez de lire dans une seule fenêtre d'éditeur. La règle informelle classique est sous quelques mégaoctets.
Files.readAllLines(path) — liste de lignes
List<String> lines = Files.readAllLines(Path.of("hosts.txt"), StandardCharsets.UTF_8);Retourne une List<String> immuable des lignes du fichier, avec les terminateurs de ligne supprimés. Même profil mémoire que readString plus la surcharge de la List — charge également tout le fichier en mémoire.
À utiliser quand : vous souhaitez indexer par numéro de ligne, trier le fichier, ou alimenter les lignes dans une boucle for (String line : lines) sans configurer de streams.
Files.readAllBytes(path) — octets bruts
byte[] raw = Files.readAllBytes(Path.of("photo.png"));L'équivalent en octets. Pas de Charset car aucun décodage n'a lieu. À utiliser pour les fichiers binaires (images, archives, exécutables) ou lorsque vous devez calculer un hachage ou rediriger des octets vers un ByteArrayInputStream.
Files.lines(path) — stream paresseux
try (Stream<String> lines = Files.lines(Path.of("app.log"), StandardCharsets.UTF_8)) {
long errors = lines.filter(l -> l.contains("ERROR")).count();
}C'est le seul lecteur intégré qui passe à l'échelle pour des fichiers arbitrairement grands. Le Stream<String> est paresseux — les lignes sont lues à la demande, pas toutes d'un coup — et se connecte directement au vocabulaire du pipeline de stream (filter, map, count, toList).
Deux points non négociables :
try-with-resources est obligatoire. Le stream possède un descripteur de fichier ouvert ; sanstry-with-resources, le fichier reste ouvert jusqu'au GC, et vous épuiserez les descripteurs de fichiers sur un serveur chargé.- Ne réutilisez pas le stream après une opération terminale. Les streams sont à usage unique.
À utiliser quand : le fichier est trop grand pour readAllLines, ou vous souhaitez que la transformation ligne par ligne se compose avec le reste de votre pipeline de stream.
BufferedReader.readLine() — la méthode classique
BufferedReader est le composant de base que les helpers modernes encapsulent. Il met en mémoire tampon les lectures sous-jacentes dans un bloc en mémoire de taille fixe pour que readLine() n'émette pas un appel système par caractère.
try (BufferedReader in = Files.newBufferedReader(Path.of("hosts.txt"), StandardCharsets.UTF_8)) {
String line;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
}Files.newBufferedReader(path) est la factory moderne ; la version classique est new BufferedReader(new FileReader("hosts.txt")) (qui utilise le charset de la plateforme sur les JDK antérieurs à 18 — fixez sur UTF-8 avec la surcharge à trois arguments). Le contrat de readLine() est :
- Retourne la ligne suivante sans son terminateur (
\n,\r, ou\r\n). - Retourne
nullen fin de fichier. La condition de boucle(line = readLine()) != nullest l'idiome établi.
BufferedReader est également un producteur de Stream<String> : reader.lines() retourne un Stream<String> soutenu par le lecteur. C'est ainsi que Files.lines est implémenté en coulisse.
Scanner — analyse jeton par jeton
Scanner lit le texte par jetons — mots, entiers, doubles, lignes, voire des correspondances regex — et est le bon outil pour lire des entrées structurées où les unités ne sont pas des lignes entières.
try (Scanner sc = new Scanner(Files.newBufferedReader(Path.of("nums.txt")))) {
while (sc.hasNextInt()) {
int n = sc.nextInt();
System.out.println(n * n);
}
}Scanner est plus lent que BufferedReader car il analyse ; il alloue de courtes chaînes et exécute des regex. Pour le traitement ligne par ligne, préférez BufferedReader. Pour des jetons typés issus d'un petit fichier (nombres, mots, entrées de type CSV), Scanner évite la couche d'analyse.
Il existe un chapitre complet sur Scanner plus loin dans cette partie — voici la variante lecture d'un fichier.
FileReader — le lecteur de caractères bruts
try (FileReader in = new FileReader("notes.txt", StandardCharsets.UTF_8)) {
int c;
while ((c = in.read()) != -1) {
System.out.print((char) c);
}
}FileReader lit les caractères directement depuis le fichier — sans mise en mémoire tampon, sans conscience des lignes, sans choix de décodage effectué pour vous (vous passez le Charset, ou acceptez la valeur par défaut de la plateforme sur les JDK antérieurs à 18). C'est la couche sur laquelle les autres s'appuient. Vous ne l'utilisez presque jamais directement dans le code applicatif ; vous l'encapsulez dans un BufferedReader.
Il est toujours utile lorsque vous souhaitez lire quelques centaines de caractères et s'arrêter — petites recherches où le coût de la mise en place d'un tampon est négligeable par rapport au coût de l'appel.
Lequel utiliser
| Scénario | Choix |
|---|---|
Petit fichier à obtenir en tant que String unique | Files.readString |
Petit fichier à obtenir en tant que List<String> | Files.readAllLines |
| Fichier binaire (image, archive) | Files.readAllBytes |
| Tout fichier avec une transformation de type stream | Files.lines (dans try-with-resources) |
| Boucle ligne par ligne, contrôle total | Files.newBufferedReader + readLine |
| Jetons typés (entiers, mots, correspondances regex) | Scanner |
| Un caractère à la fois, petit fichier | FileReader |
Le bon choix par défaut pour le cas « je veux juste charger ce petit fichier texte » est Files.readString. Le bon choix par défaut pour « traiter ce journal géant sans saturer la mémoire » est Files.lines.
Un exemple concret : le même fichier, cinq lecteurs
Le programme ci-dessous écrit un petit fichier texte, puis le lit de cinq façons différentes — readString, readAllLines, Files.lines filtré via un Predicate<String> du vocabulaire de la Partie 12, BufferedReader.readLine, et Scanner pour des entiers tokenisés. Chaque bloc affiche ce qu'il a obtenu pour que vous puissiez voir les formes côte à côte.
Ce qu'il faut retenir de l'exécution :
Files.readStringa retourné le fichier entier en tant queStringunique — simple et exactement ce qu'il vous faut pour les petites configurations et les templates. Pour un journal de 4 Go, il aurait levé uneOutOfMemoryError.Files.readAllLinesa retourné uneList<String>indexable avec les terminateurs supprimés.lines.get(0)a fonctionné parce que la liste est matérialisée en mémoire ; vous ne pourriez pas faire ça avec un stream.Files.lines(file)a été ouvert à l'intérieur d'untry-with-resources parce que le stream possède le descripteur de fichier. Le pipeline.filter(isError).count()a la même forme que tout ce qui vient de la Partie 12 — seule la source a changé.BufferedReader.readLine()a retournénullen fin de fichier. La bouclefors'est arrêtée à trois intentionnellement ici, mais l'idiome de production estwhile ((line = in.readLine()) != null).Scannera ignoré les lignes qui ne commençaient pas par un entier, puis a lu les jetons avecnextInt()jusqu'à épuisement. Le mêmeScanneraurait pu lire des doubles (nextDouble), des correspondances regex (findInLine), ou desBigIntegers — c'est pourquoi il coûte plus par jeton queBufferedReaderpar ligne.
Étape suivante
Le chapitre suivant, Écrire des fichiers en Java, couvre le côté écriture des mêmes API — Files.writeString, Files.write, BufferedWriter, PrintWriter, et les drapeaux StandardOpenOption (APPEND, CREATE_NEW, TRUNCATE_EXISTING) qui décident comment un fichier existant est géré.