Accueil > Programmer avec Java >
đ ïž Lire et Ă©crire des fichiers
Survol et attentes
Définitions
File : une classe Java qui permet de manipuler des fichiers et des répertoires. On importe cette classe avec import java.io.File;. File nous permet de créer, supprimer, renommer, etc. des fichiers et des répertoires. Au plus simple, on utilise un objet de type File comme source pour un Scanner.
- Par exemple,
Scanner fileReader = new Scanner(new File("nomDuFichier.txt")).
FileWriter : une classe Java qui permet dâĂ©crire des donnĂ©es dans un fichier texte avec la mĂ©thode write(). On importe cette classe avec import java.io.*;. On a plusieurs options pour le crĂ©er un objet de ce type mais les deux les plus communs sont :
new FileWriter("nomDuFichier.txt")-> En créant ce FileWriter, le contenu existant du fichier sera remplacé par les appels dewrite.new FileWriter("nomDuFichier.txt", true)-> En créant ce FileWriter et en assignanttrueau 2e paramÚtre, on ajoute du contenu à la fin du fichier avec les nouveaux appels dewrite.
objet anonyme : un objet qui nâa pas de nom, donc qui est utilisable seulement Ă lâendroit oĂč il est dĂ©clarĂ©. On peut crĂ©er un objet anonyme directement dans un appel de mĂ©thode.
- Par exemple,
new Scanner(new File("nomDuFichier.txt"))crĂ©e un objet Scanner qui lit le nouvel objet anonymeFile. - Cette approche remplace la dĂ©claration dâune variable pour lâobjet, p. ex.
File inputFile = new File("./data/input.txt");suivie deScanner fileReader = new Scanner(inputFile);.
chemin de fichier : sĂ©quence de dossiers Ă partir dâun point de rĂ©fĂ©rence, se terminant avec le nom complet du fichier, soit son nom et son extension.
- Pour un projet Java, le point de référence est le dossier du projet, donc le chemin commence par
./. - Par exemple,
./data/input.txtpour un fichier texteinput.txtqui se trouve dans un dossierdataĂ la racine du projet.
rĂ©fĂ©rences de dossier spĂ©ciales : On peut inclure certaines rĂ©fĂ©rences spĂ©ciales au dĂ©but dâun chemin comme :
..pour le dossier parent,.pour le dossier actuel,~pour le dossier utilisateur et/pour le dossier racine.
classe de donnĂ©es : une classe qui ne contient que des variables pour stocker des donnĂ©es. On lâutilise pour organiser des donnĂ©es lues dâun fichier texte, notamment en crĂ©ent un tableau dâobjets de cette classe.
Objectifs dâapprentissage
Ă la fin de cette leçon vous devrez ĂȘtre en mesure de :
- Reconnaßtre les ressemblances entre lire et écrire à la console et lire et écrire dans des fichiers texte.
- Appliquer les techniques de Scanner pour lire des fichiers directement ou pour lire un String.
- Utiliser les classes File et FileWriter pour manipuler des fichiers texte.
CritĂšres de succĂšs
- Je peux intĂ©grer la lecture de fichiers texte ou lâĂ©criture de fichiers texte dans mes programmes Java.
Gérer les exceptions
Parce quâon essaie dâaccĂ©der Ă une ressource - un fichier - qui nâexiste peut-ĂȘtre pas (si on lâa mal nommĂ© ou si le chemin est mal formĂ© ou si son dossier nâa pas Ă©tĂ© crĂ©e en premier), on utilise gĂ©nĂ©ralement un bloc try-catch avec ressources pour lâaction de lire ou Ă©crire dans un fichier. Le format gĂ©nĂ©ral est :
try (déclarer et initialiser la ressource) {
// code pour lire ou écrire dans le fichier
/* retourner une valeur utile et/ou assigner les valeurs lues
à des variables déclarées à l'extérieur du bloc try-catch */
} catch (Exception e) {
// message d'erreur
// retourner/assigner une valeur par défaut
}
Un avantage majeur du bloc try-catch est que la ressource est automatiquement fermĂ©e Ă la fin du bloc, mĂȘme si une exception est lancĂ©e. Cela Ă©vite les fuites de ressources et les erreurs de programmation.
Un autre avantage est quâen cas de plantage, on a une valeur connue quâon peut utiliser dans une condition pour Ă©viter que le reste du programme ne plante. Par exemple, si on sait que la valeur dâun String lue revient null en cas dâerreur, on peut tester si la valeur est null comme prĂ©-condition pour continuer le programme.
Il y a deux exemples de code dans la leçon sur les exceptions qui représentent des bons points de départ pour lire et écrire des fichiers :
Dâautres exemples dâalgorithmes incluant des exceptions sont fournis plus bas.
ConnaĂźtre la structure du fichier
Pour bien lire un fichier, il faut avoir une idée de sa structure ou, mieux, connaßtre exactement sa structure.
Cela vous permet de développer un plan pour lire le fichier, notamment :
- Est-ce que la premiÚre ligne est spéciale, avec un chiffre indiquant le nombre de lignes de données, par exemple ? -> Cela vous permet de déclarer un tableau de taille appropriée pour les données et de fixer une limite ferme pour la boucle de lecture.
- Est-ce que les données sur une ligne sont séparées par des virgules, des espaces, des tabulations, etc. ? -> Cela vous permet de configurer le Scanner correctement avec
useDelimiter() - Quels types de donnĂ©es se trouvent Ă chaque position sur une ligne ? -> Cela vous permet de configurer la suite dâinstructions
next*pour lire les données - Est-ce que les nombres décimaux utilisent un point ou une virgule pour le séparateur décimal ? -> Cela vous permet de configurer le Scanner correctement avec
useLocale()
Un objet sur mesure pour nos données
Connaissant la structure des donnĂ©es sur chaque ligne dâun fichier, vous pouvez vous casser la tĂȘte Ă savoir comment organiser toute lâinformation lue afin de pouvoir lâutiliser dans votre programme et pas seulement lâafficher Ă lâĂ©cran.
La solution est de créer une petite classe qui ne contient aucune méthode mais juste des variables pour stocker les données. On appelle cette classe une classe de données.1 2
Par exemple, plus loin on voit un exemple oĂč on veut lire le mois (String), la tempĂ©rature moyenne (double) et la prĂ©cipitation (int) pour chaque mois de lâannĂ©e. On pourrait crĂ©er une classe WeatherData avec les variables month, averageTemp et precipitation pour stocker ces donnĂ©es.
/** Définit notre propre structure de données */
class WeatherData {
String month;
double averageTemp;
int precipitation;
}
void main() {
// déclare un tableau qui contient des éléments de type WeatherData
WeatherData[] monthlyWeather = new WeatherData[12];
//... lire les données dans le tableau
}
Sans notre propre type de donnĂ©es on aurait dĂ» utiliser des tableaux sĂ©parĂ©s de String, double et int pour stocker les donnĂ©es. Cela aurait Ă©tĂ© plus difficile Ă lire et Ă maintenir. Et dans Java, on ne peut retourner quâune seule valeur dâune mĂ©thode, alors on nâaurait pas pu retourner les trois tableaux ensemble. On serait obligĂ© de dĂ©clarer ces tableaux globalement (dans la classe).
Dans la boucle de lecture, on peut assigner les valeurs lues aux 3 variables dâinstance de chacune des 12 objets de la classe WeatherData et ensuite retourner le tableau pour lâutiliser dans le reste du programme. Par exemple :
import java.io.*;
import java.util.*;
void main() {
Scanner fileReader = new Scanner(new File("weather_data2022.txt"));
WeatherData[] monthlyWeather = getWeatherData(fileReader);
filereader.close();
// ...code pour utiliser monthlyWeather
}
class WeatherData {
String month;
double averageTemp;
int precipitation;
}
WeatherData[] getWeatherData(Scanner fileReader) {
WeatherData[] weather = new WeatherData[12]; // créer un tableau de 12 objets
for (int i = 0; i < 12; i++) {
weather[i] = new WeatherData(); // initialiser chaque objet
// initialiser ses variables
weather[i].month = fileReader.next();
weather[i].averageTemp = fileReader.nextDouble();
weather[i].precipitation = fileReader.nextInt();
}
return weather; // retourner le tableau
}
Une classe comme WeatherData est le début de la programmation orientée-objet, le thÚme du cours ICS4U.
Lire un fichier texte avec un Scanner
Il y a plusieurs façons de lire un fichier texte avec un Scanner.
- Pour un fichier simple, on peut créer un seul objet Scanner (pour le fichier) et utiliser une séquence de
next*pour lire les données. - Pour un fichier qui contient des données structurées qui se répÚtent, soit un tableau de valeurs comme, p. ex., un fichier CSV, en plus du Scanner pour le fichier, on doit généralement créer une boucle pour lire les données ligne-par-ligne.
- Pour un fichier de texte non structurĂ©e (comme une histoire), on peut extraire le contenu du fichier au complet dans un String et utiliser un Scanner pour lire ce String mot-par-mot ou phrase-par-phrase ou utiliser dâautres mĂ©thodes String pour lâenquĂȘter.
Il y a plusieurs autres approches possibles, alors ces trois-ci ne sont que des points de dĂ©part. Dans tous les cas, lâalgorithme ressemble gĂ©nĂ©ralement Ă ceci :
- Déclarer des variables pour stocker les données lues.
- Extraire les donnĂ©es dans un bloc try-catch avec ressources (la dĂ©claration du Scanner pour le fichier); dĂ©finir des valeurs par dĂ©faut en cas dâerreur.
- Si la valeur lue est la valeur en cas dâerreur (clause de garde) :
- quitter le programme (ou Ă©viter autrement lâutilisation du code qui dĂ©pend des valeurs lues).
- Sinon :
- utiliser les données lues dans le reste du programme.
Lire un fichier simple
Prenons lâexemple dâun fichier qui conserve le meilleur pointage obtenu en jouant votre jeu. Le fichier highscore.txt pourrait ressembler Ă ceci :
David 99
On sait quâil contient juste un nom (de type String) et un pointage (de type int). On peut lire ce fichier avec le code suivant :
void main() {
String name; // variables déclarées à l'extérieur du bloc try-catch
int score;
try (Scanner fileReader = new Scanner(new File("./highscore.txt"))) {
name = fileReader.next(); // valeurs en cas de succĂšs
score = fileReader.nextInt();
} catch (Exception e) {
System.out.println("Erreur de lecture du fichier.");
name = null; // valeurs en cas d'erreur
score = 0;
}
// précondition ("clause de garde")
if (name == null) {
System.out.println("Aucun pointage n'a été enregistré.");
// possiblement quitter le programme avec return;
}
// ...code pour utiliser name et score
}
Lire un fichier structuré
Prenons lâexemple dâun fichier qui conserve les tempĂ©ratures moyennes pour chaque mois de lâannĂ©e, incluant les valeurs maximum et minimum, et la quantitĂ© de prĂ©cipitation en mm. Le fichier weather.txt pourrait ressembler Ă ceci :
Ottawa
Mois;Tmoy(C);Tmin(C);Tmax(C);Précip(mm)
Janvier;-9,1;-18,2;11,4;114
Février;-11,3;-22,4;10,2;98
Mars;-8,2;-12,3;17,3;140
...
Décembre;-12,3;-16,4;12,3;87
On voit :
- la premiĂšre ligne donne juste le nom de la ville
- la deuxiÚme ligne donne les noms des colonnes et les unités de mesure
- il y 12 lignes de données par la suite (1 pour chaque mois)
- les valeurs sont séparées par des points-virgules
- les nombres décimaux utilisent une virgule comme séparateur décimal
Si on sâintĂ©resse juste aux valeurs des tempĂ©ratures moyennes et de la prĂ©cipation, on peut prĂ©voir les Ă©tapes suivantes :
- configurer le Scanner pour lire les données avec
useDelimiter(";")etuseLocale(Locale.CANADA_FRENCH) - des commandes spéciales pour lire la ville et ignorer la ligne avec les noms de colonne
- une boucle qui fait 12 itérations pour lire les données mensuelles ligne-par-ligne : un
nextpour le mois, 1nextDoublepour la tempĂ©rature, 2nextquâon ignore et unnextIntpour les prĂ©cipitations
Une implémentation possible utilisant la classe WeatherData décrite plus haut est la suivante :
void main() {
WeatherData[] weather; // variable déclarée à l'extérieur du bloc try-catch
String city;
try (Scanner fileReader = new Scanner(new File("./weather.txt"))) {
fileReader.useDelimiter(";");
fileReader.useLocale(Locale.CANADA_FRENCH);
city = fileReader.nextLine(); // premiĂšre ligne
fileReader.nextLine(); // ignorer l'en-tĂȘte Ă la 2e ligne
weather = new WeatherData[12]; // pour les 12 lignes de données
for (int i = 0; i < 12; i++) {
weather[i] = new WeatherData(); // initialiser chaque objet
weather[i].month = fileReader.next(); // initialiser ses variables
weather[i].averageTemp = fileReader.nextDouble();
fileReader.next(); // ignorer la température minimale
fileReader.next(); // ignorer la température maximale
weather[i].precipitation = fileReader.nextInt();
}
} catch (Exception e) {
System.out.println("Erreur de lecture du fichier.");
weather = null; // valeurs en cas d'erreur
}
// précondition ("clause de garde")
if (weather == null) {
System.out.println("Aucune donnée météo n'a été enregistrée.");
// possiblement quitter le programme avec return;
}
// ...code pour utiliser weather
}
Lire un fichier non structuré
Prenons lâexemple dâun fichier qui contient une histoire. Le fichier story.txt pourrait ressembler Ă ceci :
Il était une fois, dans une galaxie lointaine, trÚs lointaine...
Ici le but est juste dâextraire le texte au complet afin de lâanalyser par la suite. Une technique efficace est dâutiliser le caractĂšre spĂ©ciale \\Z qui dĂ©signe la fin dâun fichier comme dĂ©limiteur du Scanner. Lire le ficher revient alors Ă un seul appel de next :
void main(){
String story; // variable déclarée à l'extérieur du bloc try-catch
try (Scanner fileReader = new Scanner(new File("./story.txt"))) {
fileReader.useDelimiter("\\Z"); // configurer le Scanner
story = fileReader.next(); // lire le fichier au complet
} catch (Exception e) {
System.out.println("Erreur de lecture du fichier.");
story = null; // valeurs en cas d'erreur
}
// précondition ("clause de garde")
if (story == null) {
System.out.println("Aucune histoire n'a été enregistrée.");
// possiblement quitter le programme avec return;
}
// ...code pour utiliser story,
// incluant potentiellement d'autres Scanners
}
đ ïž Pratique
Travaillez dans le répertoire GitHub partagé par votre enseignant pour la pratique et les exercices.
Lire un fichier simple
- Créez un fichier
FileReading1.javaet y ajouter sa méthode main. - Créez un fichier texte
moodJournal.txtqui contient la ligne suivante :2024-04-27 heureux. Implémentez un algorithme dansmainqui :- Lit le contenu du fichier
moodJournal.txtdans un bloc try-catch et affiche son contenu Ă la console. - Lit le contenu du fichier
moodJournal.txtdans un bloc try-catch et assigne les valeurs individuelles Ă des variables pour la date et lâhumeur (deuxString). PrĂ©parer un message Ă lâextĂ©rieur du bloc try-catch qui affiche la date et lâhumeur. - (DĂFI) Lit le contenu du fichier
moodJournal.txtdans un bloc try-catch et assigne les valeurs individuelles Ă des variables pour lâannĂ©e, le mois, le jour (3int) et lâhumeur (String).Trois pistes pour Scanner les parties de la date : (1) modifiez le dĂ©limiteur du Scanner (â-â et â â), (2) utilisez dâabord
nextpour la date entiĂšre et dĂ©clare un Scanner sur ce texte avec un dĂ©limiteur de â-â pour saisir les int, (3) utilisersplit("-")sur la date entiĂšre etInteger.parseIntsur chaque Ă©lĂ©ment du tableau.
- Lit le contenu du fichier
Lire un fichier structuré
- Créer un fichier
FileReading2.javaet y ajouter sa méthode main.
Ăcrire dans un fichier
- Créer un fichier
FileWriting.javaet y ajouter sa méthode main.
-
Java offre aussi une structure de données appelée
Recordqui est plus spĂ©cifique quâune classe mais exactement pour ce genre de situation, mais qui masque les donnĂ©es, ce qui est un concept plus avancĂ© quâon voit seulement en 12e annĂ©e. â© -
Les exemples de code ci-dessous appliquent le JEP 463 (classe abstraite, méthode
maindâinstance), alors seulement la classe interne pour la structure de donnĂ©es est dĂ©clarĂ©e dans le code. â©