Généralités
---
Il y a quatre classes "mères", abstraites, pour traiter les flots de données héritant directement d'Object :
pour traiter des flots d'octets (fichiers binaire)
la classe InputStream
la classe OutputStream
pour traiter des flots de caractères (fichiers textes)
la classe Reader
la classe Writer
Beaucoup de classes héritent de ces classes de base. Nous ne traiterons que de celles qui servent dans les applications les plus courantes.
On peut envelopper en cascade les streams/readers/writers pour disposer des fonctionnalités nécessaires. Exemple les InputStreams et les DataInputStreams :
Lire des lignes avec un BuffereReader
import java.io.*;
class SaisieClavier
{
public static void main (String[] argv)
throws IOException
{
String ligne;
BufferedReader
entree = new BufferedReader (new InputStreamReader(System.in));
ligne = entree.readLine();
while(ligne.length()
> 0)
{
System.out.prinltn(ligne);
ligne = entree.readLine();
}
}
}
On utilise deux classes de java.io, la classe InputStreamReader et la classe BufferedReader.
La classe InputStreamReader admet un constructeur InputStreamReader(InputStream), c'est-à-dire un constructeur qui admet en paramètre un flot d'entrée.
System.in est une instance de la classe InputStream.
Avec une instance de InputStreamReader, on ne peut grosso modo que lire des caractères un à un.
La classe BufferedReader a un constructeur qui prend en argument une instance de Reader dont hérite la classe InputStreamReader. Cette classe permet en particulier de lire une ligne d'un texte, mais en revanche, on ne peut pas lui demander de lire un entier, un double etc...
Lire des entiers, des réels, des mots avec un StreamTokenizer
StreamTokenizer entree= new StreamTokenizer (new
InputStreamReader(System.in));
boolean fin = false;
int type;
while (! fin) {
type=entree.nextToken();
switch (type) {
case
StreamTokenizer.TT_NUMBER:
System.out.println("Nombre : "+entree.nval);
break;
case
StreamTokenizer.TT_WORD:
System.out.println("Mot : "+entree.sval);
break;
default:
fin
= true;
}
}
On utilise ici une instance de StreamTokenizer qui est un analyseur syntaxique plutôt rudimentaire.
Un constructeur de la classe StreamTokenizer prend en paramêtre une instance de Reader.
La méthode nextToken() de la classe StreamTokenizer retourne le type de l'unité lexicale suivante, type qui est caractérisé par une constante entière. Cela peut être :
· TT_NUMBER si l'unité lexicale représente un nombre. Ce nombre se trouve alors dans le champ nval de l'instance de StreamTokenizer. Ce champ est de type double.
· TT_WORD si l'unité lexicale représente une chaîne de caractères. Cette chaîne se trouve alors dans le champ sval de l'instance du StreamTokenizer.
· TT_EOL si l'unité lexicale représente une fin de ligne. La fin de ligne n'est reconnue comme unité lexicale que si on a utilisé l'instruction
nomDuStreamTokenizer.eolIsSignificant(true);
· TT_EOF s'il s'agit du signe de fin de fichier.
Lire/écrire des caractères dans un fichier
FileReader
lecteur;
FileWriter
ecrivain;
int c;
lecteur = new FileReader("essai.txt");
ecrivain = new
FileWriter("copie_essai.txt");
ecrivain.write("copie
de essai.txt\n");
while((c = lecteur.read()) != -1)
ecrivain.write(c);
lecteur.close();
ecrivain.close();
Si le fichier essai.txt
contient :
bonjour
rebonjour
le fichier copie_essai.txt contient après exécution :
copie de essai.txt
bonjour
rebonjour
La classe FileReader permet de lire des caractères dans un fichier.
La classe FileWriter permet d'écrire des caractères dans un fichier
Analogie avec fgetc et fputc du C
Ecrire diverses choses dans un fichier texte
On utilise ici une instance de PrintWriter, dont un constructeur prend en argument un Writer dont la classe BufferedWriter hérite.
Vous pouvez utiliser avec une instance de PrintWriter
les méthodes print et println de la même façon
qu'avec System.out
(qui est de la classe PrintStream).
La classe PrintWriter (qui hérite de la classe Writer) ne fait qu'améliorer la classe PrintStream (qui hérite de OutputStream).
On aurait pu plus simplement initialiser ecrivain par :
ecrivain = new PrintWriter(new FileWriter(argv[0]));
mais alors les écritures n'utiliseraient pas de mémoire-tampon.
PrintWriter
ecrivain;
int n = 5;
ecrivain = new
PrintWriter(new BufferedWriter
(new FileWriter(argv[0])));
ecrivain.println("bonjour,
comment cela va-t-il ?");
ecrivain.println("un peu difficile ?");
ecrivain.println("On peut mettre des entiers : "+n);
ecrivain.print("On peut mettre des instances de Object : ");
ecrivain.println(new Integer(36));
ecrivain.close();
Apres exécution, le fichier indiqué contient :
bonjour, comment cela va-t-il ?
un peu difficile ?
On peut mettre des entiers 5
On peut mettre des instances de Object : 36
Lire dans un fichier texte
On compose un BufferdReader avec un FileReader.
BufferedReader lecteurAvecBuffer = null;
String ligne;
try
{
lecteurAvecBuffer = new BufferedReader
(new
FileReader(argv[0]));
}
catch(FileNotFoundException exc)
{
System.out.println("Erreur d'ouverture");
}
while ((ligne =
lecteurAvecBuffer.readLine()) != null)
System.out.println(ligne);
lecteurAvecBuffer.close();
Il s'agit maintenant de lire des entiers dans un fichier ne contenant que des entiers et d'en faire la somme.
import java.io.*;
class LireEntiers
{
public
static void main (String[] argv) throws
IOException
{
int
somme = 0;
FileReader fichier = new FileReader(argv[0]);
StreamTokenizer
entree = new StreamTokenizer(fichier);
while(entree.nextToken() == StreamTokenizer.TT_NUMBER)
{
somme += (int)entree.nval;
}
System.out.println("La somme vaut : " + somme);
fichier.close();
}
}
Avec un fichier contenant :
5 3 6 2 7
-10 23
on obtient :
La
somme vaut : 36
Écrire dans un fichier binaire
Pour traiter des fichiers binaires, c'est-à-dire qui contienne éventuellement autres choses que des caractères (des int(s) écrits en binaire et pas comme des chaînes de caractères...), on peut utiliser la classe DataOutputStream. Celle-ci à un constructeur qui prend en paramètre une instance de la classe OutputStream. On aurait pû initialiser notre variable ecrivain par :
DataOutputStream(new FileOutputStream(argv[0]));
Cela aurait été identique en apparence, mais si on compose comme il est fait
ici avec une instance de BufferedOutputStream,
les écritures dans le fichier utilisent une mémoire-tampon, ce qui gagne du
temps.
La classe OutputStream dispose de beaucoup de méthodes ; nous donnons des exemples ci-dessous des principales méthodes.
import java.io.*;
class EcrireFichierBinaire
{
public
static void main(String[] argv) throws IOException
{
DataOutputStream ecrivain;
ecrivain =
new DataOutputStream(new
BufferedOutputStream
(new
FileOutputStream(argv[0])));
ecrivain.writeUTF("bonjour");
ecrivain.writeInt(3);
ecrivain.writeLong(100000);
ecrivain.writeFloat((float)2.0);
ecrivain.writeDouble(3.5);
ecrivain.writeChar('a');
ecrivain.writeBoolean(false);
ecrivain.writeUTF("au revoir");
System.out.println(ecrivain.size());
ecrivain.close();
}
}
Après l'exécution de la commande
java
EcrireFichierBinaire essai
on obtient à lécran : 47
et le fichier essai est assez illisible.
Lire dans un fichier binaire
La classe DataInputStream nous permet de lire un fichier binaire. D'autre méthodes de cette classe pourront être consultées.
Si on ne désire pas utiliser de mémoire-tampon, on peut initialiser la variable lecteur plus simplement par :
lecteur= new
DataInputStream(new
FileInputStream(argv[0]));
import java.io.*;
class LireFichierBinaire
{
public
static void main(String[] argv) throws
IOException
{
DataInputStream
lecteur;
lecteur=
new DataInputStream(new BufferedInputStream
(new
FileInputStream(argv[0])));
System.out.println(lecteur.readUTF());
System.out.println(lecteur.readInt());
System.out.println(lecteur.readLong());
System.out.println(lecteur.readFloat());
System.out.println(lecteur.readDouble());
System.out.println(lecteur.readChar());
System.out.println(lecteur.readBoolean());
System.out.println(lecteur.readUTF());
lecteur.close();
}
}
Pour la
commande :
java
LireFichierBinaire essai
où le fichier essai est issu de l'exécution du programme de
l'exemple précédent, on obtient :
bonjour
3
100000
2.0
3.5
a
false
au revoir
la classe File
On se propose ici d'exploiter la classe java.io.File. Celle-ci permet de lister les fichiers d'un répertoire, de savoir si un fichier existe, de renommer un fichier, de supprimer un fichier... Une partie des méthodes de la classe java.io.File sont illustrées ci-dessous.
Pour exécuter notre programme, on doit indiquer sur la ligne
de commande le nom d'un répertoire. On indiquera en fait le répertoire dans
lequel s'exécute notre programme de façon à vérifier que le fichier qui sera
créé au cours du programme figurera bien ensuite dans ce répertoire.
import java.io.*;
class EssaiFile
{
public
static void main(String[] argv) throws
IOException
{
File repertoire;
File
fichier=null;
File
nouveauFichier;
String[]
listeFichiers;
PrintWriter ecrivain;
repertoire=new File(argv[0]);
if (!repertoire.isDirectory()) System.exit(0);
fichier=new File("fichier.essai");
System.out.println("le fichier "+fichier.getName()+
(fichier.exists()?" existe":" n'existe pas"));
//en sortie : le fichier fichier.essai n'existe pas
ecrivain=new PrintWriter(new
FileOutputStream("fichier.essai"));
ecrivain.println("bonjour");
ecrivain.close();
System.out.println("le fichier "+fichier.getName()+
(fichier.exists()?" existe":" n'existe pas"));
//en sortie : le fichier fichier.essai existe
System.out.println("Sa longueur est "+fichier.length());
//en sortie : Sa longueur est 8
System.out.println("Son chemin complet est \n
"+fichier.getAbsolutePath());;
//en sortie :
//Son chemin complet est
// /inf/aquilon/infmd/charon/public_html/coursJava/fichiersEtSaisies/fichier.essai
System.out.println();
listeFichiers=repertoire.list();
for (int
i=0;i < listeFichiers.length;i++)
System.out.println(listeFichiers[i]);
System.out.println();
nouveauFichier=new
File("autre.essai");
fichier.renameTo(nouveauFichier);
System.out.println("le fichier "+fichier.getName()+
(fichier.exists()?" existe":" n'existe plus"));
//en sortie : le fichier fichier.essai n'existe plus
System.out.println("le fichier
"+nouveauFichier.getName()+
(nouveauFichier.exists()?" existe":" n'existe
pas"));
//en sortie : le fichier autre.essai existe
nouveauFichier.delete();
}
}
la sérialisation
La sérialisation consiste à transformer des objets
en flots d’octets (l’opération inverse étant appelée dé-sérialisation) qui
peuvent être :
-
écrits dans des
fichiers (on sauve ainsi un objet) et lus depuis des fichiers (on restaure
ainsi un objet précédemment sauvé).
-
envoyés à travers un
réseau (en utilisant par exemple des sockets ou des mécanismes de plus haut
niveau comme RMI ou CORBA)
En java pour rendre les objets d’une classe
sérialisables il suffit que la classe implémente l’interface
java.io.Serializable et que les attributs de cette classe soient eux-mêmes
sérialisables (note : les attributs de type scalaire sont automatiquement
sérialisables ainsi que la plupart des classes de l’API java) :
class
Personne implements java.io.Serializable {
private String nom ;
private String prenom ;
private int age ;
public Personne(String prenom, String
nom, int age) {
this.nom = nom;
this.prenom
= prenom ;
this.age = age ;
}
…
}
Dans quelques cas particuliers (listes doublement
chaînées par exemple) il faudra développer deux méthodes pour indiquer comment
doit se faire la sérialisation et la dé-sérialisation :
private void writeObject(java.io.ObjectOutputStream out)
throws IOException
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
Ces méthodes utiliseront les méthodes des classes ObjectInputStream
et ObjectOutputStream pour lire et écrire les attributs.
Ensuite, pour écrire des objets sur un flot (et dans un fichier) il faudra utiliser la classe java.io.ObjectOutputStream de la façon suivante :
FileOutputStream fos = new FileOutputStream("fichier.bin");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject("Chaîne de caractère");
oos.writeObject(new Date());
oos.writeObject(new Personne(“Bob”,”Sinclar”,30));
oos.close();
Et pour les lire (note : il faut utiliser le même ordre, sinon on aura une exception ClassCastException lors du « cast ») java.io.ObjectInputStream :
FileInputStream fis = new FileInputStream("fichier.bin");
ObjectInputStream ois = new ObjectInputStream(fis);
String today = (String) ois.readObject();
Date date = (Date) ois.readObject();
Personne p = (Personne) ois.readObject();
ois.close();