Présentation
Cet article aborde des tâches courantes que les développeurs d’applications peuvent rencontrer, en particulier dans le cadre des applications web, telles que :
- Lecture et écriture de fichiers texte
- Récupération de texte, d’images et de JSON depuis le web
- Exploration des fichiers dans un répertoire
- Lecture d’un fichier ZIP
- Création d’un fichier ou d’un répertoire temporaire
La Java API propose de nombreuses autres fonctionnalités, détaillées dans le tutoriel sur l’API Java I/O.
Nous nous concentrerons ici sur les améliorations apportées à l’API depuis Java 8, notamment :
- UTF-8 est devenu l’encodage par défaut pour les opérations d’entrée/sortie depuis Java 18 (JEP 400 : UTF-8 par défaut)
- La classe
java.nio.file.Files
, introduite dans Java 7, a bénéficié de nouvelles méthodes dans les versions 8, 11 et 12 - La classe
java.io.InputStream
a également reçu des méthodes utiles dans Java 9, 11 et 12 - Les classes
java.io.File
etjava.io.BufferedReader
sont désormais largement obsolètes, bien qu’elles soient encore fréquemment mentionnées dans les recherches en ligne et les discussions d’IA.
Lecture de Fichiers Texte
Pour lire un fichier texte dans une chaîne, vous pouvez procéder comme suit :
String contenu=Files.readString(chemin);
Dans cet exemple, chemin
est une instance de java.nio.Path
, obtenue de cette manière :
var chemin=Path.of("/usr/share/dict/words");
Avant Java 18, il était fortement recommandé de spécifier l’encodage des caractères lors des opérations de lecture ou d’écriture de chaînes. Aujourd’hui, l’encodage le plus courant est UTF-8, mais pour des raisons de compatibilité, Java utilisait l' »encodage de la plateforme », qui peut être un encodage hérité sur Windows. Pour garantir la portabilité, les opérations d’entrée/sortie de texte nécessitaient le paramètre StandardCharsets.UTF_8
. Cela n’est plus nécessaire.
Si vous souhaitez obtenir le fichier sous forme de lignes, utilisez :
List lignes=Files.readAllLines(chemin);
Pour les fichiers volumineux, traitez les lignes de manière paresseuse en tant que Stream
:
try (Stream lignes=Files.lines(chemin)) {
. . .
}
Utilisez également Files.lines
si vous pouvez traiter naturellement les lignes avec des opérations de flux (comme map
, filter
). Notez que le flux retourné par Files.lines
doit être fermé. Pour garantir cela, utilisez une instruction try-with-resources, comme dans l’exemple précédent.
Il n’y a plus de raison valable d’utiliser la méthode readLine
de java.io.BufferedReader
.
Pour diviser votre entrée en éléments autres que des lignes, utilisez un java.util.Scanner
. Par exemple, voici comment lire des mots, séparés par des caractères non alphabétiques :
Stream tokens=new Scanner(chemin).useDelimiter("PL+").tokens();
La classe Scanner
propose également des méthodes pour lire des nombres, mais il est généralement plus simple de lire l’entrée sous forme d’une chaîne par ligne, ou d’une seule chaîne, puis de l’analyser.
Faites attention lors de l’analyse des nombres à partir de fichiers texte, car leur format peut dépendre de la locale. Par exemple, l’entrée 100.000
représente 100.0 dans la locale américaine mais 100000.0 dans la locale allemande. Utilisez java.text.NumberFormat
pour une analyse spécifique à la locale. Alternativement, vous pouvez utiliser Integer.parseInt
/Double.parseDouble
.
Écriture de Fichiers Texte
Pour écrire une chaîne dans un fichier texte, il suffit d’un seul appel :
String contenu=. . .;
Files.writeString(chemin, contenu);
Si vous avez une liste de lignes plutôt qu’une seule chaîne, utilisez :
List lignes=. . .;
Files.write(chemin, lignes);
Pour une sortie plus générale, utilisez un PrintWriter
si vous souhaitez utiliser la méthode printf
:
var writer=new PrintWriter(chemin.toFile());
writer.printf(locale, "Bonjour, %s, l'année prochaine vous aurez %d ans !%n", nom, age + 1);
Notez que printf
est spécifique à la locale. Lors de l’écriture de nombres, assurez-vous de les formater correctement. Au lieu d’utiliser printf
, envisagez d’utiliser java.text.NumberFormat
ou Integer.toString
/Double.toString
.
Étrangement, à partir de Java 21, il n’existe pas de constructeur PrintWriter
avec un paramètre Path
.
Si vous n’utilisez pas printf
, vous pouvez recourir à la classe BufferedWriter
et écrire des chaînes avec la méthode write
.
var writer=Files.newBufferedWriter(chemin);
writer.write(ligne); // N'écrit pas de séparateur de ligne
writer.newLine();
N’oubliez pas de fermer le writer
une fois que vous avez terminé.
Lecture Depuis un Flux Entrant
Une des raisons les plus courantes d’utiliser un flux est de lire des données depuis un site web.
Si vous devez définir des en-têtes de requête ou lire des en-têtes de réponse, utilisez le HttpClient
:
HttpClient client=HttpClient.newBuilder().build();
HttpRequest request=HttpRequest.newBuilder()
.uri(URI.create("https://horstmann.com/index.html"))
.GET()
.build();
HttpResponse response=client.send(request, HttpResponse.BodyHandlers.ofString());
String resultat=response.body();
Cela peut sembler excessif si vous souhaitez simplement obtenir les données. Dans ce cas, utilisez :
InputStream in=new URI("https://horstmann.com/index.html").toURL().openStream();
Ensuite, lisez les données dans un tableau d’octets et, si nécessaire, transformez-les en chaîne :
byte[] octets=in.readAllBytes();
String resultat=new String(octets);
Ou transférez les données vers un flux de sortie :
OutputStream out=Files.newOutputStream(chemin);
in.transferTo(out);
Notez qu’aucune boucle n’est nécessaire si vous souhaitez simplement lire tous les octets d’un flux entrant.
Mais avez-vous vraiment besoin d’un flux entrant ? De nombreuses API vous offrent la possibilité de lire à partir d’un fichier ou d’une URL.
Votre bibliothèque JSON préférée dispose probablement de méthodes pour lire à partir d’un fichier ou d’une URL. Par exemple, avec Jackson jr :
URL url=new URI("https://dog.ceo/api/breeds/image/random").toURL();
Map resultat=JSON.std.mapFrom(url);
Voici comment lire l’image d’un chien à partir de l’appel précédent :
URL url=new URI(result.get("message").toString()).toURL();
BufferedImage img=javax.imageio.ImageIO.read(url);
Cela est préférable à l’utilisation d’un flux d’entrée pour la méthode read
, car la bibliothèque peut tirer parti d’informations supplémentaires provenant de l’URL pour déterminer le type d’image.
L’API des Fichiers
La classe java.nio.file.Files
offre un ensemble complet d’opérations sur les fichiers, telles que la création, la copie, le déplacement et la suppression de fichiers et de répertoires. Le tutoriel sur les bases du système de fichiers fournit une description détaillée. Dans cette section, je vais mettre en avant quelques tâches courantes.
Parcourir les Entrées dans les Répertoires et Sous-Répertoires
Dans la plupart des cas, vous pouvez utiliser l’une des deux méthodes. La méthode Files.list
permet de visiter toutes les entrées (fichiers, sous-répertoires, liens symboliques) d’un répertoire.
try (Stream entries=Files.list(pathToDirectory)) {
. . .
}
Utilisez une déclaration try-with-resources pour garantir que l’objet de flux, qui suit l’itération, sera fermé.
Si vous souhaitez également explorer les entrées des répertoires descendants, utilisez plutôt la méthode Files.walk
.
Stream entries=Files.walk(pathToDirectory);
Vous pouvez ensuite utiliser les méthodes de flux pour cibler les entrées qui vous intéressent et collecter les résultats :
try (Stream entries=Files.walk(pathToDirectory)) {
List htmlFiles=entries.filter(p -> p.toString().endsWith("html")).toList();
. . .
}
Voici d’autres méthodes pour parcourir les entrées de répertoire :
- Une version surchargée de
Files.walk
vous permet de limiter la profondeur de l’arborescence parcourue. - Deux méthodes
Files.walkFileTree
offrent un contrôle accru sur le processus d’itération, en notifiant unFileVisitor
lors de la première et de la dernière visite d’un répertoire. Cela peut être utile, notamment pour vider et supprimer un arbre de répertoires. Consultez le tutoriel sur le parcours de l’arborescence des fichiers pour plus de détails. À moins que vous n’ayez besoin de ce contrôle, utilisez la méthode plus simpleFiles.walk
. - La méthode
Files.find
fonctionne commeFiles.walk
, mais vous fournissez un filtre qui inspecte chaque chemin et sesBasicFileAttributes
. Cela est légèrement plus efficace que de lire les attributs séparément pour chaque fichier. - Deux méthodes
Files.newDirectoryStream(Path)
renvoient des instances deDirectoryStream
, qui peuvent être utilisées dans des bouclesfor
améliorées. Il n’y a pas d’avantage par rapport à l’utilisation deFiles.list
. - Les méthodes héritées
File.list
ouFile.listFiles
renvoient des noms de fichiers ou des objetsFile
. Celles-ci sont désormais obsolètes.
Travailler avec des Fichiers ZIP
Depuis Java 1.1, les classes ZipInputStream
et ZipOutputStream
fournissent une API pour le traitement des fichiers ZIP. Cependant, l’API est un peu maladroite. Java 8 a introduit un système de fichiers ZIP beaucoup plus agréable :
try (FileSystem fs=FileSystems.newFileSystem(pathToZipFile)) {
. . .
}
La déclaration try-with-resources garantit que la méthode close
est appelée après les opérations sur le fichier ZIP. Cette méthode met à jour le fichier ZIP pour refléter les modifications dans le système de fichiers.
Vous pouvez ensuite utiliser les méthodes de la classe Files
. Ici, nous obtenons une liste de tous les fichiers dans le fichier ZIP :
try (Stream entries=Files.walk(fs.getPath("/"))) {
List filesInZip=entries.filter(Files::isRegularFile).toList();
}
Pour lire le contenu d’un fichier, utilisez simplement Files.readString
ou Files.readAllBytes
:
String contents=Files.readString(fs.getPath("/LICENSE"));
Vous pouvez supprimer des fichiers avec Files.delete
. Pour ajouter ou remplacer des fichiers, utilisez simplement Files.writeString
ou Files.write
.
Créer des Fichiers et Répertoires Temporaires
Il m’arrive souvent de devoir collecter des entrées utilisateur, de produire des fichiers et d’exécuter un processus externe. Dans ce cas, j’utilise des fichiers temporaires, qui disparaissent après le prochain redémarrage, ou un répertoire temporaire que je supprime après l’achèvement du processus.
J’utilise les deux méthodes Files.createTempFile
et Files.createTempDirectory
pour cela.
Path filePath=Files.createTempFile("myapp", ".txt");
Path dirPath=Files.createTempDirectory("myapp");
Cela crée un fichier ou un répertoire temporaire à un emplacement approprié (/tmp
sous Linux) avec le préfixe donné et, pour un fichier, un suffixe.
Synthèse
Les recherches sur le web et les discussions avec l’IA peuvent suggérer des codes inutilement complexes pour des opérations d’E/S courantes. Il existe souvent de meilleures alternatives :
- Vous n’avez pas besoin d’une boucle pour lire ou écrire des chaînes ou des tableaux d’octets.
- Vous n’avez même pas besoin d’un flux, d’un lecteur ou d’un écrivain.
- Familiarisez-vous avec les méthodes
Files
pour créer, copier, déplacer et supprimer des fichiers et des répertoires. - Utilisez
Files.list
ouFiles.walk
pour parcourir les entrées de répertoire. - Utilisez un système de fichiers ZIP pour traiter les fichiers ZIP.
- Évitez la classe héritée
File
.