Les entrées/sorties Java (sérialisation, accès aux fichiers et

Transcription

Les entrées/sorties Java (sérialisation, accès aux fichiers et
Année 2008-2009
Les entrées/sorties Java
(sérialisation, accès aux chiers et connexion réseau)
Nicolas Baudru
mél : [email protected]
page web : nicolas.baudru.perso.esil.univmed.fr
1
Introduction
Lors de la conception d'un logiciel, il n'est pas rare de vouloir lire ou écrire dans un
chier (par ex., pour sauvegarder des données), ou bien se connecter à un serveur.
Indépendamment de la méthode employée, vous utiliserez des techniques
d'entrées/sorties (E/S ou I/O en anglais) qui sont pratiquement toujours les
mêmes : on écrit ou on lit des données sur/via un support quelconque
(généralement un chier ou une connexion réseau).
Dans ce cours nous allons étudier ces techniques d'E/S via trois cas d'utilisation :
I la sérialisation
I la lecture ou l'écriture de/dans des chiers
I les connexions réseaux
A chaque fois, il nous faudra utiliser des ots d'E/S.
2
Le concept de ots
L'API d'E/S Java comprend deux types de ots :
I les ots de communication, qui représentent des destinations et des sources
telles que des chiers ou des sockets réseau
I les ots de traitement qui ne fonctionnent que s'ils sont chaînés à d'autres
ots.
En général, il est nécessaire de chaîner au moins deux ots : l'un pour représenter
la connexion et l'autre pour les appels de méthodes.
Exemple : FileOutputStream (voir plus loin) a des méthodes pour écrire des octets
dans un chier. Mais comme nous voulons écrire des objets, il faut un ot de
traitement de plus haut niveau.
La possibilité de mélanger diérentes combinaisons de ots de communication et
de traitement procure une très grande souplesse et permet de personnaliser
l'utilisation de ces chaînages.
3
La sérialisation
Supposons que vous écrivez un jeu (le jeu du zoo par exemple), et que vous
souhaitez enregistrer l'état de votre jeu, pour le restaurer plus tard. Deux
méthodes (au moins) sont disponibles :
I
I
interroger chaque objet, puis enregistrer la valeur de leurs variables d'instance
dans un chier que vous créez. Cette méthode est laborieuse mais le chier
produit est lisible par l'homme et peut donc être lu par d'autres applications
non-Java.
ou le faire à la OO, c'est-à-dire en sérialisant les objets à sauvegarder. Cette
méthode est plus ecace et plus adaptée au concept objet, mais le résultat
est un chier qui n'a aucun sens pour l'homme. Il est cependant beaucoup
plus facile et sûr pour votre programme de restaurer les chiers issus de la
sérialisation plutôt que des chiers textes.
4
Écrire un objet sérialisé dans un chier
1 Créer
un FileOutputStream. Cet objet sait comment se connecter à un chier
et même en créer un.
FileOutputStream fos = new FileOutputStream ( " MonZoo . ser " );
2 Créer
un ObjectOutputStream. Cet objet permet d'écrire des objets, mais il ne
peut pas se connecter directement à un chier. Il a besoin pour cela d'un intermédiaire , ici un FileOutputStream. On va alors chaîner les deux ots, le
ObjectOutputStream au FileOutputStream.
ObjectOuputStream
3 Écrire
oos = new ObjectOutputStream ( fos );
les objets à sauvegarder dans MonZoo.ser .
oos . writeObject ( monChien );
oos . writeObject ( monCanard );
4 Fermer
ObjectOutputStream. La fermeture du premier ot entraîne
automatiquement celle des autres.
oos . close ();
5
Écrire un objet sérialisé dans un chier
Objet
cr
té
es
00101100
11010010
100
s
an
it d
l'objet est sérialisé
ObjectOutputStream
é
aîn
ch
transformé en octets
et écrit dans
à
0010110011010010100
FileOutputStream
6
Fichier
Qu'est-ce qu'un objet sérialisé ?
Les objets sur le tas ont un état. Cet état est représenté par la valeur de ses
variables d'instance. Ce sont ces valeurs qui diérencient deux instances d'une
même classe.
La sérialisation sauvegarde la valeur des variables d'instance dans un chier, ainsi
que quelques informations indispensables à la JVM pour restaurer l'objet (comme
par exemple son type).
Si les variables d'instance de l'objet sont de type primitif, alors la sauvegarde est
simple. Mais que doit-on sauvegarder si certaines variables d'instance sont des
références ?
En fait, dans ce dernier cas, il faut aussi sérialiser tous les objets référencés. Et
tous les objets que ces objets référencent doivent l'être aussi. Et ainsi de suite...
Heureusement, tout ceci s'eectue de manière automatique par la JVM...
7
Que peut-on sérialiser ?
Si vous voulez qu'une classe soit sérialisable, il faut qu'elle implémente l'interface
Serialisable. Cette interface est dite de type marqueur car elle n'a aucune méthode
à implémenter. Son seul rôle est d'annoncer à la JVM qu'elle est sérialisable.
Si une classe est marquée sérialisable, toutes ses sous-classes le sont aussi par
héritage.
Pour pouvoir sérialiser un objet, il faut que tout objet référencé par l'une des
variables d'instance de cet objet soit aussi sérialisable, et ainsi de suite.
Si une variable d'instance ne peut pas être sauvegardée parce qu'elle n'est pas
sérialisable, vous pouvez la déclarer transient. Dans ce cas le processus de
sérialisation ignorera complètement cette variable.
Pourquoi une variable ne serait-elle pas sérialisable ?
8
Que peut-on sérialiser ? Exemple
import java . io .*; // Serialisable est dans ce package .
public class Canin implements Serialisable { }
public class Loup extends Canin { }
public class Chien { }
public class Coq { }
public class BasseCour implement Serialisable {
Coq c = new coq ; }
public class Chenil implements Serialisable {
transient Chien c = new Chien (); }
Quels sont les objets sérialisables ?
9
Questions
Pourquoi toutes les classes ne sont-elles pas sérialisables ?
Que devient une variable transient lors de la restauration d'un objet ?
Que se passe-t-il si plusieurs variables pointent sur un même objet ?
Que signie avoir une sous-classe sérialisable
alors que sa superclasse ne l'est pas ?
10
La désérialisation
Tout l'intérêt de sauvegarder un objet est de pouvoir le restaurer le moment venu.
Le processus de désérialisation est très similaire à celui de la sérialisation :
1 Chaîner
un FileInputStream et un ObjectInputStream
FileInputStream fis = new FileInputStream ( " MonZoo . ser " );
ObjectInputStream ois = new ObjectInputStream ( fis );
2 A
chaque invocation de readObject, l'objet suivant est récupéré.
Object un = ois . readObject ();
Object deux = ois . readObject ();
3 Convertir
les objets.
Loup monLoup = ( Loup ) un ;
4 Fermer
les ots.
ois . close ();
11
Le processus de désérialisation
1 L'objet est lu dans le ot.
2 La JVM détermine le type
de l'objet et essaie de charger la classe de l'objet
(elle lance une exception en cas d'échec).
3 Elle
alloue au nouvel objet de l'espace sur le tas, mais n'appelle pas le
construcuteur de l'objet sérialisé.
4 Si
l'objet a une classe non-sérialisable quelque part dans son arbre
généalogique, le constructeur de cette classe non-sérialisable s'éxécute (et donc
tous les constructeurs des superclasses de cette classe non-sérialisable). Ainsi
toutes les superclasses, à partir de la première non-sérialisable, vont réinitialiser
leur état.
5 Les
variables d'instance reçoivent les valeurs de l'état sérialisé. Les variables
temporaires reçoivent null pour les références et la valeur par défaut pour les types
primitifs. Les variables transient sont réinitialisées par un appel du constructeur
adéquat.
Les variables statiques12sont-elles sérialisées ?
Numéro de version et désérialisation
Que se passerait-il si une classe était modiée avant la désérialisation ?
Modications pouvant empêcher la désérialisation :
I Supprimer une variable d'instance,
I ...
Et quoi d'autres ?
Modication sans problème :
I Ajouter de nouvelles variables d'instance à la classe,
I ...
Et quoi d'autres ?
Heureusement Java met en oeuvre un mécanisme d'estampillage de l'objet sérialisé
par un numéro de version (serialVersionID) an de détecter tout changement.
13
Ecrire du texte dans un chier
Ecrire du texte (des données de type String) dans un chier est similaire à
l'écriture d'un objet sérialisé, sauf que l'on veut écrire des chaînes de caractères et
non pas des octets dans le chier.
Pour réaliser cela, il sut d'utiliser un FileWriter plutôt qu'un FileOuputStream.
Puisque les chaînes de caractères vont être écrites tel quel, il n'est pas nécessaire
d'utiliser un ot de traitement comme ObjectOuputStream.
import java . io .*; // ce package contient FileWriter
class EcrireTexte {
public static void main ( String [] args ) {
try { // tout le code E / S doit être dans un try / catch
FileWriter fw = new FileWriter ( " MonFic . txt " );
// si ce fichier n ' existe pas , il sera créé
fw . write ( " bonjour " );
fw . close (); // Ferme le flot
}
catch ( IOException ex ) { ex . printStackTrace ();}
}
}
14
La classe Java.io.File
Cette classe représente un chier sur le disque, mais pas son contenu. Un objet
File peut être vu comme le nom de chemin d'un chier ou d'un répertoire.
Un objet File n'a donc pas de méthodes de lecture ou d'écriture, mais il possède
une propriété très utile : il permet de représenter un chier de façon beaucoup
plus sûre qu'un String. De plus vous pouvez par exemple vérier que le chemin est
valide avant de le transmettre comme argument à une autre méthode.
La plupart des classes de ots de données dont le constructeur accepte un String
accepte aussi un objet de type File (ex. FileWriter, FileInputSream, ...).
15
Exemple d'utilisation de File
Créer un objet représentant un chier existant :
File f = new File ( " MonFic . txt " );
Créer un répertoire :
File rep = new File ( " MonRep " );
rep . mkdir ();
Lister le contenu d'un répertoire :
if ( rep . isDirectory ()) {
String [] contenuRep = rep . list ();
for ( int i = 0; i < contenuRep . length ; i ++) {
System . out . println ( contenuRep [ i ]);
}
}
Obtenir le chemin absolu d'un chier ou d'un répertoire :
System . out . println ( rep . getAbsolutePath ());
Supprimer un chier ou un répertoire :
boolean supprime = f . delete ();
16
Les buers
Les buers fournissent un contenant temporaire pour grouper les données jusqu'à
ce qu'il soit plein. De cette façon, vous pouvez limiter le nombre d'accès au disque
dur (qui prennent beaucoup de temps).
"Marseille"
String
t
es
rit
éc
ns
da
"Marseille"
"Paris" "Lille"
BufferedWriter
ns
é
aîn
da
ch
"Marseille Paris Lille"
FileWriter
Lille
Paris
Marseille
MonFic.txt
Exemple :
FileWriter fw = new FileWriter ( " monFic . txt " );
BufferedWriter bw = new BufferedWriter ( fw );
Dans cet exemple les données seront écrites sur le disque lorsque le buer sera
plein. En eet, ce dernier transfèrera alors toutes ses données au FileWriter qui
écrira toutes les données sur le disque en une seule fois.
Si vous voulez envoyer les données avant que le buer soit plein, il sut de
le vider par un appel de bw.ush().
17
Lire du texte dans un chier
import java . io .*; // A ne pas oublier
class LireFichier {
public static void main ( String [] args ) {
try {
File f = new File ( " MonFic . txt " );
FileReader fr = new FileReader ( f );
// Flot de communication pour les caractères ,
// qui se connecte à un fichier
BufferedReader br = new BufferedReader ( fr );
// Flot de traitement pour les caractères ( buffer ).
// Ce flot est chaîné au FileReader
String ligne = null ; // contiendra chaque ligne
while (( ligne = br . readLine ()) != null ) {
System . out . println ( ligne );
}
br . close ();
} catch ( IOException ex ) { ex . printStackTrace ();}
}
}
18

Documents pareils