12. Chapitre 9. Enregistrement du score▲
Après qu'il se soit terminé, un programme est effacé de la mémoire. Cela signifie que toutes les classes, méthodes et variables disparaissent jusqu'à ce que tu exécutes à nouveau ce programme. Si tu souhaites sauvegarder certains résultats de l'exécution du programme, il faut les enregistrer dans des fichiers sur un disque, une cassette, une carte mémoire ou un autre périphérique capable de stocker des données pendant une longue période. Dans ce chapitre, tu vas apprendre comment enregistrer des données sur disque à l'aide des flux (streams) Java. Fondamentalement, tu ouvres un flux entre ton programme et un fichier sur disque. Si tu veux lire des données sur le disque, il te faut un flux d'entrée (input stream) ; si tu écris des données sur le disque, ouvre un flux de sortie (output stream). Par exemple, si un joueur gagne une partie et que tu veux sauvegarder son score, tu peux l'enregistrer dans le fichier scores.txt en utilisant un flux de sortie.
Un programme lit ou écrit les données dans un flux en série - octet après octet, caractère après caractère, etc. Comme ton programme utilise différents types de données tels que String, int, double et autres, tu dois utiliser un flux Java approprié, par exemple un flux d'octets (byte stream), un flux de caractères (character stream) ou un flux de données (data stream).
Les classes qui fonctionnent avec des flux de fichiers sont situées dans les paquetages java.io. et java.nio.
Quel que soit le type de flux que tu vas utiliser, tu dois respecter les trois étapes suivantes dans ton programme :
- Ouvrir un flux qui pointe sur un fichier.
- Lire ou écrire des données dans ce flux.
- Fermer le flux.
12-A. Flux d'octets▲
Si tu crées un programme qui lit un fichier puis affiche son contenu sur l'écran, tu dois savoir quel type de données contient le fichier. Par contre, un programme qui se contente de copier des fichiers d'un endroit à un autre n'a même pas besoin de savoir s'il s'agit d'images, de texte ou de musique. De tels programmes chargent le fichier original en mémoire sous la forme d'un ensemble d'octets, puis les écrivent dans le fichier de destination, octet après octet, à l'aide des classes Java FileInputStream et FileOutputStream.
L'exemple suivant montre comment utiliser la classe FileInputStream pour lire un fichier graphique nommé abc.gif, situé dans le répertoire c:\exercices. Si tu utilises un ordinateur sous Microsoft Windows, pour éviter la confusion avec les caractères spéciaux Java qui commencent par une barre oblique inversée, utilise des barres doubles dans ton code pour séparer les noms de répertoires et de fichier : « c:\\exercices ». Ce petit programme n'affiche pas l'image, mais des nombres qui correspondent à la façon dont l'image est stockée sur un disque. Chaque octet a une valeur entière positive comprise entre 0 et 255, que la classe LecteurOctets affiche en la délimitant par des espaces.
Je te prie de noter que la classe LecteurOctets ferme le flux dans le bloc finally. N'appelle jamais la méthode close() à l'intérieur du bloc try/catch juste après avoir fini de lire le fichier, mais fais-le dans le bloc finally. Sinon, en cas d'exception, le programme sauterait par-dessus l'instruction close() barrée et le flux ne serait pas fermé ! La lecture se termine quand la méthode FileInputStream.read() retourne la valeur négative -1.
import
java.io.FileInputStream;
import
java.io.IOException;
public
class
LecteurOctets {
public
static
void
main
(
String[] args) {
FileInputStream monFichier =
null
;
try
{
// Ouvre un flux pointant sur le fichier
monFichier =
new
FileInputStream
(
"c:
\\
exercices
\\
abc.gif"
);
while
(
true
) {
int
valeurEntièreOctet =
monFichier.read
(
);
System.out.print
(
" "
+
valeurEntièreOctet);
if
(
valeurEntièreOctet ==
-
1
) {
// Nous avons atteint la fin du fichier
// Sortons de la boucle
break
;
}
}
// Fin de la boucle while
// monFichier.close(); pas à cet endroit
}
catch
(
IOException exception) {
System.out.println
(
"Impossible de lire le fichier : "
+
exception.toString
(
));
}
finally
{
try
{
monFichier.close
(
);
}
catch
(
Exception exception1){
exception1.printStackTrace
(
) ;
}
System.out.println
(
"Lecture du fichier terminée."
);
}
}
}
L'extrait de code suivant écrit plusieurs octets, représentés par des nombres entiers, dans le fichier xyz.dat, à l'aide de la classe FileOutputStream :
int
données[] =
{
56
, 230
, 123
, 43
, 11
, 37
}
;
FileOutputStream monFichier =
null
;
try
{
// Ouvre le fichier xyz.dat et y enregistre
// les données du tableau
monFichier =
new
FileOutputStream
(
"xyz.dat"
);
for
(
int
i =
0
; i <
données.length; i++
) {
monFichier.write
(
données[i]);
}
}
catch
(
IOException exception) {
System.out.println
(
"Impossible d'écrire dans le fichier :"
+
exception.toString
(
));
}
finally
{
try
{
monFichier.close
(
);
}
catch
(
Exception exception1) {
exception1.printStackTrace
(
);
}
}
12-B. Flux à tampon▲
Jusqu'ici nous avons lu et écrit les données un octet à la fois, ce qui implique que le programme LecteurOctets devra accéder 1000 fois au disque pour lire un fichier de 1000 octets. Mais l'accès aux données sur le disque est bien plus lent que la manipulation de données en mémoire. Pour minimiser le nombre de tentatives d'accès au disque, Java fournit ce qu'on appelle des tampons (buffers), qui sont des sortes de « réservoirs de données ».
La classe BufferedInputStream permet de remplir rapidement la mémoire tampon avec des données de FileInputStream. Un flux à tampon charge d'un seul coup dans un tampon en mémoire un gros paquet d'octets depuis un fichier. Ensuite, la méthode read() lit chaque octet dans le tampon beaucoup plus rapidement qu'elle ne le ferait sur le disque.
Ton programme peut connecter des flux comme un plombier connecte deux tuyaux. Modifions l'exemple qui lit un fichier. Les données sont d'abord déversées du FileInputStream dans le BufferedInputStream, puis passées à la méthode read() :
FileInputStream monFichier =
null
;
BufferedInputStream tampon =
null
;
try
{
monFichier =
new
FileInputStream
(
"c:
\\
exercices
\\
abc.gif"
);
// Connecte les flux
tampon =
new
BufferedInputStream
(
monFichier);
while
(
true
) {
int
valeurOctet =
tampon.read
(
);
System.out.print
(
valeurOctet +
" "
);
if
(
valeurOctet ==
-
1
)
break
;
}
}
catch
(
IOException exception) {
exception.printStackTrace
(
);
}
finally
{
try
{
tampon.close
(
);
monFichier.close
(
);
}
catch
(
IOException exception1) {
exception1.printStackTrace
(
);
}
}
Quelle est la taille de ce tampon ? Cela dépend du Java, mais tu peux régler sa taille et voir si cela rend la lecture de fichier un peu plus rapide. Par exemple, pour affecter au tampon une taille de 5000 octets, utilise le constructeur à deux arguments :
BufferedInputStream tampon =
new
BufferedInputStream
(
monFichier, 5000
);
Les flux à tampon ne modifient pas le type de lecture ; ils la rendent seulement plus rapide.
BufferedOutputStream fonctionne de la même façon, mais avec la classe FileOutputStream.
int
données[] =
{
56
, 230
, 123
, 43
, 11
, 37
}
;
FileOutputStream monFichier =
null
;
BufferedOutputStream tampon =
null
;
try
{
monFichier =
new
FileOutputStream
(
"xyz.dat"
);
// Connecte les flux
tampon =
new
BufferedOutputStream
(
monFichier);
for
(
int
i =
0
; i <
données.length; i++
) {
tampon.write
(
données[i]);
}
}
catch
(
IOException exception) {
exception.printStackTrace
(
);
}
finally
{
try
{
tampon.flush
(
);
tampon.close
(
);
monFichier.close
(
);
}
catch
(
IOException exception1) {
exception1.printStackTrace
(
);
}
}
Pour t'assurer que tous les octets du tampon sont envoyés au fichier, appelle la méthode flush() (vider) lorsque l'écriture dans le BufferedOutputStream est terminée.
12-C. Arguments de la ligne de commande▲
Notre programme LecteurOctets stocke le nom du fichier abc.gif directement dans son code, ou, comme disent les développeurs, le nom de fichier est écrit en dur dans le programme. Cela signifie que, pour créer un programme similaire qui lit le fichier xyz.gif, tu dois modifier le code et recompiler le programme, ce qui n'est pas agréable. Il vaudrait mieux passer le nom du fichier depuis la ligne de commande, lors du lancement du programme.
Tu peux exécuter tous les programmes Java avec des arguments de ligne de commande, par exemple :
java LecteurOctets xyz.gif
Dans cet exemple, nous passons à la méthode main() de LecteurOctets un seul argument - xyz.gif. Si tu t'en souviens, la méthode main() a un argument :
public
static
void
main
(
String[] arguments)
Effectivement, c'est un tableau de String que Java passe à la méthode main(). Si tu lances un programme sans aucun argument sur la ligne de commande, ce tableau est vide. Dans le cas contraire, le nombre d'éléments de ce tableau est exactement le même que celui des arguments passés au programme sur la ligne de commande.
Voyons comment utiliser ces arguments de ligne de commande dans une classe très simple qui ne fait que les afficher :
public
class
TestArguments {
public
static
void
main
(
String[] arguments) {
// Combien d'arguments m'a-t-on fourni ?
int
nombreArguments =
arguments.length;
for
(
int
i =
0
; i <
nombreArguments; i++
) {
System.out.println
(
"On m'a fourni "
+
arguments[i]);
}
}
}
La capture d'écran suivante montre ce qu'il se passe si on exécute ce programme avec deux arguments - xyz.gif et 250. La valeur xyz.gif est placée par Java dans l'élément arguments[0] et la seconde dans arguments[1].
Les arguments de la ligne de commande sont toujours passés à un programme comme des Strings. Il est de la responsabilité du programme de convertir les données dans le type de données approprié. Par exemple :
int
monScore =
Integer.parseInt
(
arguments[1
]);
C'est toujours une bonne chose de vérifier si la ligne de commande contient le bon nombre d'arguments. Fais-le au tout début de la méthode main(). Si le programme ne reçoit pas les arguments attendus, il doit le signaler en affichant un message bref et s'arrêter immédiatement en utilisant la méthode spéciale System.exit():
public
static
void
main
(
String[] arguments) {
if
(
arguments.length !=
2
) {
System.out.println
(
"Merci de fournir deux arguments, par exemple : "
);
System.out.println
(
"java TestArguments xyz.gif 250"
);
// Sortie du programme
System.exit
(
0
);
}
}
À la fin de ce chapitre, tu devras écrire un programme qui copie des fichiers. Pour que ce programme fonctionne avec n'importe quels fichiers, les noms des fichiers source et destination doivent être passés au programme en tant qu'arguments de la ligne de commande.
Tu peux tester tes programmes dans Eclipse, qui permet aussi de fournir des arguments de ligne de commande à tes programmes. Dans la fenêtre Exécuter, sélectionne l'onglet (x)=Arguments et entre les valeurs requises dans la boîte Arguments de programme.
La boîte Arguments VM est utilisée pour passer des paramètres à Java. Ces paramètres permettent de demander plus de mémoire pour ton programme, régler finement la performance de Java… Tu trouveras dans la section Autres lectures la référence d'un site web qui décrit ces paramètres en détail.
12-D. Lecture de fichiers texte▲
Java utilise des caractères de deux octets pour stocker les lettres. Les classes FileReader et FileWriter sont très pratiques pour travailler avec des fichiers texte. Ces classes peuvent lire un fichier texte, soit caractère par caractère à l'aide de la méthode read(), soit ligne par ligne à l'aide de la méthode readLine(). Les classes FileReader et FileWriter ont aussi leurs contreparties BufferedReader et BufferedWriter pour accélérer le travail avec des fichiers.
La classe LecteurScores lit le fichier scores.txt ligne à ligne et le programme se termine quand la méthode readLine() renvoie null, ce qui signifie fin de fichier.
À l'aide d'un éditeur de texte quelconque, crée le fichier c:\scores.txt avec le contenu suivant :
David 235
Daniel 190
Anna 225
Gregory 160
Exécute le programme LecteurScores ci-dessous et il affichera le contenu de ce fichier. Ajoute d'autres lignes au fichier de scores et exécute à nouveau le programme pour vérifier que les nouvelles lignes sont aussi affichées.
import
java.io.FileReader;
import
java.io.BufferedReader;
import
java.io.IOException;
public
class
LecteurScores {
public
static
void
main
(
String[] arguments) {
FileReader monFichier =
null
;
BufferedReader tampon =
null
;
try
{
monFichier =
new
FileReader
(
"c:
\\
scores.txt"
);
tampon =
new
BufferedReader
(
monFichier);
while
(
true
) {
// Lit une ligne de scores.txt
String ligne =
tampon.readLine
(
);
// Vérifie la fin de fichier
if
(
ligne ==
null
)
break
;
System.out.println
(
ligne);
}
// Fin du while
}
catch
(
IOException exception) {
exception.printStackTrace
(
);
}
finally
{
try
{
tampon.close
(
);
monFichier.close
(
);
}
catch
(
IOException exception1) {
exception1.printStackTrace
(
);
}
}
}
// Fin de main
}
Si ton programme doit écrire un fichier texte sur un disque, utilise l'une des méthodes write() surchargées de la classe FileWriter. Ces méthodes permettent d'écrire un caractère, un String ou un tableau entier de caractères.
FileWriter possède plusieurs constructeurs surchargés. Si tu ouvres un fichier en écriture en ne fournissant que son nom, ce fichier sera remplacé par un nouveau à chaque fois que tu exécuteras le programme :
FileWriter fichierSortie =
new
FileWriter
(
"c:
\\
scores.txt"
);
Si tu souhaites ajouter des données à la fin d'un fichier existant, utilise le constructeur à deux arguments (true signifie ici mode ajout) :
FileWriter fichierSortie =
new
FileWriter
(
"c:
\\
scores.txt"
, true
);
La classe EnregistreurScores écrit trois lignes dans le fichier c:\scores.txt à partir du tableau scores.
import
java.io.FileWriter;
import
java.io.BufferedWriter;
import
java.io.IOException;
public
class
EnregistreurScores {
public
static
void
main
(
String[] arguments) {
FileWriter monFichier =
null
;
BufferedWriter tampon =
null
;
String[] scores =
new
String[3
];
// Entre des scores dans le tableau
scores[0
] =
"M. Dupont 240"
;
scores[1
] =
"M. Durand 300"
;
scores[2
] =
"M. Pemieufer 190"
;
try
{
monFichier =
new
FileWriter
(
"c:
\\
scores.txt"
);
tampon =
new
BufferedWriter
(
monFichier);
for
(
int
i =
0
; i <
scores.length; i++
) {
// Ecrit le tableau de chaînes dans scores.txt
tampon.write
(
scores[i]);
System.out.println
(
"Écriture de : "
+
scores[i]);
}
System.out.println
(
"Écriture du fichier terminée."
);
}
catch
(
IOException exception) {
exception.printStackTrace
(
);
}
finally
{
try
{
tampon.flush
(
);
tampon.close
(
);
monFichier.close
(
);
}
catch
(
IOException e1) {
e1.printStackTrace
(
);
}
}
}
// Fin de main
}
La sortie de ce programme ressemble à ceci :
Écriture de : M. Dupont 240
Ecriture de : M. Durand 300
Écriture de : M. Pemieufer 190
Écriture du fichier terminée.
12-E. Classe File (fichier)▲
La classe java.io.File fournit de nombreuses méthodes utiles, qui permettent de renommer un fichier, de supprimer un fichier, de vérifier si le fichier existe, etc. Mettons que ton programme enregistre des données dans un fichier et qu'il ait besoin d'afficher un message pour avertir l'utilisateur si ce fichier existe déjà. Pour ce faire, tu dois créer une instance de l'objet File en lui donnant le nom du fichier, puis appeler la méthode exists(). Si cette méthode retourne true, le fichier a été trouvé et tu dois afficher un message d'avertissement. Sinon, c'est que ce fichier n'existe pas.
File unFichier =
new
File
(
"abc.txt"
);
if
(
unFichier.exists
(
)) {
// Affiche un message ou utilise un JOptionPane
// pour afficher un avertissement.
}
Le constructeur de la classe File ne crée pas réellement un fichier - il crée juste en mémoire une instance de cet objet qui pointe sur le fichier réel. Si tu dois vraiment créer un fichier sur le disque, utilise la méthode createNewFile().
Voici quelques-unes des méthodes utiles de la classe File.
Nom de la méthode |
Fonctionnalité |
---|---|
createNewFile() |
Crée un nouveau fichier, vide, du nom utilisé pour l'instanciation de la classe File. Ne crée un nouveau fichier que s'il n'existe pas déjà un fichier du même nom. |
delete() |
Supprime un fichier ou un répertoire. |
renameTo() |
Renomme un fichier. |
length() |
Retourne la longueur d'un fichier en octets. |
exists() |
Retourne true si le fichier existe. |
list() |
Retourne un tableau de chaînes contenant les noms des fichiers/répertoires contenus dans un répertoire donné. |
lastModified() |
Retourne l'heure et la date de dernière modification du fichier. |
mkDir() |
Crée un répertoire. |
L'extrait de code ci-dessous renomme le fichier clients.txt en clients.txt.bak. Si le fichier .bak existe déjà, il est remplacé.
File fichier =
new
File
(
"clients.txt"
);
File sauvegarde =
new
File
(
"clients.txt.bak"
);
if
(
sauvegarde.exists
(
)) {
sauvegarde.delete
(
);
}
fichier.renameTo
(
sauvegarde);
Même si nous n'avons travaillé dans ce chapitre que sur des fichiers situés sur le disque de ton ordinateur, Java te permet de créer des flux pointant vers des machines distantes sur un réseau d'ordinateurs. Ces ordinateurs peuvent être situés assez loin les uns des autres. Par exemple, la NASA utilise Java pour contrôler les robots de la mission Mars Rovers et je suis sûr qu'ils se sont contentés de pointer leurs flux sur Mars. :-)
12-F. Autres lectures▲
1. Options de ligne de commande Java |
12-G. Exercices▲
Écris le programme de copie de fichier FileCopy en combinant les fragments de code de la section sur les flux d'octets. |
java CopieFichier
c
:
\temp\scores.txt
c
:
\sauvegardes\scores2.txt
12-H. Exercices pour les petits malins▲
|
|