Programmation Java pour les enfants, les parents et les grands-parents


précédentsommairesuivant

XI. 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.

XI-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.

 
Sélectionnez
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 :

 
Sélectionnez
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();
    }
  }

XI-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.

Image non disponible

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() :

 
Sélectionnez
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 :

 
Sélectionnez
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.

 
Sélectionnez
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.

XI-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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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].

Image non disponible

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 :

 
Sélectionnez
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():

 
Sélectionnez
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);
  }
}

A 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.

Image non disponible

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.

XI-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.

A l'aide d'un éditeur de texte quelconque, crée le fichier c:\scores.txt avec le contenu suivant :

 
Sélectionnez
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.

 
Sélectionnez
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 :

 
Sélectionnez
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) :

 
Sélectionnez
FileWriter fichierSortie = new FileWriter("c:\\scores.txt", true);

La classe EnregistreurScores écrit trois lignes dans le fichier c:\scores.txt à partir du tableau scores.

 
Sélectionnez
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("Ecriture de : " + scores[i]);
	}
      System.out.println("Ecriture 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 :

 
Sélectionnez
Ecriture de : M. Dupont 240
Ecriture de : M. Durand 300
Ecriture de : M. Pemieufer 190
Ecriture du fichier terminée.

XI-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.

 
Sélectionnez
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é.

 
Sélectionnez
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. :-)

XI-F. Autres lectures

Image non disponible

1. Options de ligne de commande Java

http://java.sun.com/ (...) /java.html

2. Utilisation des flux fichier

http://java.sun.com/ (...) /filestreams.html

XI-G. Exercices

Image non disponible

Ecris le programme de copie de fichier FileCopy en combinant les fragments de code de la section sur les flux d'octets.

Ouvre deux flux (entrée et sortie) et appelle les méthodes read() et write() dans la même boucle. Utilise les arguments de la ligne de commande pour passer au programme les noms des fichiers source et cible, par exemple :

 
Sélectionnez
java CopieFichier 
            c:\temp\scores.txt 
            c:\sauvegardes\scores2.txt

XI-H. Exercices pour les petits malins

Image non disponible

Crée un programme Swing qui permette à l'utilisateur de sélectionner les noms de fichiers à copier en utilisant la classe JFileChooser, qui crée une fenêtre standard de sélection de fichier. Cette fenêtre doit s'ouvrir quand l'utilisateur clique sur l'un des boutons Parcourir. Tu as quelques lignes de code à écrire pour afficher le nom de fichier sélectionné dans le champ textuel correspondant.

Image non disponible

Quand l'utilisateur clique sur le bouton Copier, le code de la méthode actionPerformed() doit copier le fichier sélectionné. Essaie de réutiliser le code de l'exercice précédent sans effectuer de copier/coller.


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2015 Yakov Fain. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.