W3docs

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 :

  1. Files.readString(path) — fichier entier en tant que String.
  2. Files.readAllLines(path) — fichier entier en tant que List<String>.
  3. Files.readAllBytes(path) — fichier entier en tant que byte[].
  4. Files.lines(path) — fichier en tant que Stream<String>, en mode paresseux.
  5. 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 ; sans try-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 null en fin de fichier. La condition de boucle (line = readLine()) != null est 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énarioChoix
Petit fichier à obtenir en tant que String uniqueFiles.readString
Petit fichier à obtenir en tant que List<String>Files.readAllLines
Fichier binaire (image, archive)Files.readAllBytes
Tout fichier avec une transformation de type streamFiles.lines (dans try-with-resources)
Boucle ligne par ligne, contrôle totalFiles.newBufferedReader + readLine
Jetons typés (entiers, mots, correspondances regex)Scanner
Un caractère à la fois, petit fichierFileReader

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.

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • Files.readString a retourné le fichier entier en tant que String unique — simple et exactement ce qu'il vous faut pour les petites configurations et les templates. Pour un journal de 4 Go, il aurait levé une OutOfMemoryError.
  • Files.readAllLines a retourné une List<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'un try-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é null en fin de fichier. La boucle for s'est arrêtée à trois intentionnellement ici, mais l'idiome de production est while ((line = in.readLine()) != null).
  • Scanner a ignoré les lignes qui ne commençaient pas par un entier, puis a lu les jetons avec nextInt() jusqu'à épuisement. Le même Scanner aurait pu lire des doubles (nextDouble), des correspondances regex (findInLine), ou des BigIntegers — c'est pourquoi il coûte plus par jeton que BufferedReader par 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é.

Pratique

Pratique
Vous devez traiter un journal serveur de 5 Go ligne par ligne, en comptant combien de lignes contiennent le mot `ERROR`. Quel lecteur convient le mieux ?
Vous devez traiter un journal serveur de 5 Go ligne par ligne, en comptant combien de lignes contiennent le mot `ERROR`. Quel lecteur convient le mieux ?
Was this page helpful?