[Plan du site]
Vous êtes ici ---
> Le Site du Zéro
> Les tutoriels
> Officiels
> Programmation
> Programmation en Java > Java et la programmation événementielle > Les flux d'entrées / sorties (1/2)
> Lecture du tutoriel
Les flux d'entrées / sorties (1/2)
Je vous préviens tout de suite, ce chapitre sera certainement le plus difficile de tous. Déjà, parce que vous allez voir beaucoup de nouvelles choses, mais surtout parce que la notion de fichier et de son traitement n'est pas évidente au premier abord.
Ne vous inquiétez pas trop tout de même : je vais tenter de vous expliquer tout cela dans le détail, mais là, je ne vous cache pas qu'il faudra de la pratique pour tout assimiler...
J'espère que le fait de découper cette partie en deux vous facilitera le travail.
Alors, pourquoi utiliser les entrées / sorties ?
Tout simplement parce qu'il peut être utile de sauvegarder des données, traitées par votre programme, afin de les réutiliser !
Je tiens à signaler que je ne vais pas trop approfondir le sujet. Sinon il faudrait tout une partie rien que les différents type de fichier (.zip, .properties...). Et je tiens à dire aux puristes que je ne ferai que mettre en garde contre les exceptions que la lecture ou l'écriture dans des fichiers peut engendrer !
Avant de nous lancer dans la programmation avec des fichiers, nous devons voir ce que sont les entrées / sorties, ou communément ce qui est appelé les flux d'entrée / sortie.
Une entrée / sortie en Java consiste en un échange de données entre le programme et une autre source, qui peut être :
- la mémoire ;
- un fichier ;
- le programme lui-même ;
- ...
Pour réaliser cela, Java emploie ce qu'on appelle un
stream (qui signifie flux).
Celui-ci joue le rôle de médiateur entre la source des données et sa destination.
Nous allons voir que Java met à notre disposition toute une panoplie d'objets afin de pouvoir communiquer de la sorte. Tout comme l'objet
Scanner que je ne vous présente plus maintenant, ces objets sont rangés dans un
package :
java.io (
io signifie ici
in/out, pour
entrée / sortie). Il n'y a pas loin d'une cinquantaine de classes dans ce package... Heureusement, nous n'allons pas toutes les aborder.
Toute opération sur les entrées / sorties, en Java, doit suivre le schéma suivant :
- ouverture du flux ;
- lecture du flux ;
- fermeture du flux.
Je ne vous cache pas qu'il existe plusieurs objets différents qui ont tous des spécificités de travail avec les flux. Dans un souci de simplicité, nous n'aborderons que ceux qui traitent avec des fichiers (une liste des types d'objets est disponible en
annexe).
Sachez aussi que Java a décomposé les objets traitant des flux en deux catégories :
- les objets travaillant avec des flux d'entrée (in), lecture de flux ;
- les objets travaillant avec des flux de sortie (out), écriture de flux.
Et comme je vous l'ai dit plus haut, il existe un objet Java pour chaque cas.
Par exemple, il existe :
- l'objet FileInputStream pour ouvrir un flux vers un fichier en lecture ;
- l'objet FileOutputStream pour ouvrir un flux vers un fichier en écriture ;
- ...
Ces objets peuvent prendre une chaîne de caractères, précisant le chemin et le nom du fichier à utiliser, en paramètres de leurs constructeurs ! Cependant, il peut être de bon ton d'utiliser un objet
File.
Celui-ci permet de faire des tests sur le fichier ou de récupérer des informations le concernant.
C'est par là que nous commencerons.
Avant de commencer, créez-vous un fichier, avec l'extension que vous voulez pour le moment, et enregistrez-le à la racine de votre projet Eclipse ! Personnellement, je me suis fait un fichier
test.txt dont voici le contenu :
Code : Autre1
2
3
| Voici une ligne de test.
Voici une autre ligne de test.
Et comme je suis motivé, en voici une troisième ! |
Le nom du dossier contenant mon projet s'appelle "
IO", mon fichier .txt est à cette adresse "
D:\Mes documents\Codage\SDZ\Java-SDZ\IO\test.txt".
Voyez :
Maintenant, nous allons voir ce que sait faire l'objet
File.
Vous allez voir que cet objet est très simple à utiliser et que ses méthodes sont très explicites.
Pour commencer, nous allons retourner en mode console !
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 | //Package à importer afin d'utiliser l'objet File
import java.io.File;
public class Main {
public static void main(String[] args) {
//Création de l'objet File
File f = new File("test.txt");
System.out.println("Chemin absolu du fichier : " + f.getAbsolutePath());
System.out.println("Nom du fichier : " + f.getName());
System.out.println("Est-ce qu'il existe ? " + f.exists());
System.out.println("Est-ce un répertoire ? " + f.isDirectory());
System.out.println("Est-ce un fichier ? " + f.isFile());
System.out.println("Affichage des lecteurs racines du PC : ");
for(File file : f.listRoots())
{
System.out.println(file.getAbsolutePath());
try {
int i = 1;
//On parcourt la liste des fichiers et répertoires
for(File nom : file.listFiles()){
//S'il s'agit d'un dossier, on ajoute un "/"
System.out.print("\t\t" + ((nom.isDirectory()) ? nom.getName()+"/" : nom.getName()));
if((i%4) == 0){
System.out.print("\n");
}
i++;
}
System.out.println("\n");
} catch (NullPointerException e) {
//L'instruction peut générer un NullPointerException s'il n'y a
//pas de sous-fichier ! !
}
}
}
}
|
Le résultat est bluffant :
Vous conviendrez que les méthodes de cet objet peuvent s'avérer très utiles !
Nous venons d'en essayer quelques-unes et nous avons même listé les sous-fichiers et sous-dossiers de nos lecteurs racines.
Vous pouvez aussi effacer le fichier grâce la méthode
delete()
, créer des répertoires avec la méthode
mkdir()
(le nom passé à l'objet ne devra pas avoir de ".")...
Maintenant que vous en savez un peu plus sur cet objet, nous allons pouvoir commencer à travailler avec notre fichier créé précédemment !
C'est par le biais de ces objets que nous allons pouvoir :
- lire dans un fichier ;
- écrire dans un fichier.
Ces classes héritent mutuellement des classes abstraites
InputStream et
OutputStream, présentes dans le package
java.io
.
Ce sont deux des super-classes présentes dans ce package et une grande partie des objets traitant des flux d'entrée / sortie héritent de ces objets.
Comme vous l'avez sans doute remarqué, il y a une hiérarchie de classe pour les traitements
in et une autre pour les traitements
out.
Ne vous y trompez pas, les classes héritant de
InputStream sont destinées à la lecture et les classes héritant de
OutputStream se chargent de l'écriture !
C'est bizarre, j'aurais dit le contraire...
Oui, comme beaucoup de gens au début. Mais c'est uniquement parce que vous situez les flux par rapport à vous, et non à votre programme !
Lorsque ce dernier va lire des informations dans un fichier, ce sont des informations qu'il reçoit, et par conséquent, elles s'apparentent par conséquent à une entrée :
in.
Sachez tout de même que, lorsque vous tapez au clavier, cette action est considérée comme un flux d'entrée !
Au contraire, lorsque celui-ci va écrire dans un fichier (ou à l'écran, souvenez-vous de
System.out.println
), par exemple, il va faire sortir des informations : donc, pour lui, ce mouvement de données correspond à une sortie :
out.
Nous allons enfin commencer à travailler avec notre fichier...
Le but est d'aller en lire le contenu et de le copier dans un autre, dont nous spécifierons le nom dans notre programme, par le biais d'un programme Java !
Ce code est assez compliqué... Donc accrochez-vous à vos claviers !
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 | //Package à importer afin d'utiliser les objets
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
//Nous déclarons nos objets en dehors du bloc try/catch
FileInputStream fis;
FileOutputStream fos;
try {
//On instancie nos objets.
//fis va lire le fichier et
//fos va écrire dans le nouveau !
fis = new FileInputStream(new File("test.txt"));
fos = new FileOutputStream(new File("test2.txt"));
//On créer un tableau de byte
//pour dire de combien en combien on va lire les données
byte[] buf = new byte[8];
//On crée une variable de type int
//pour affecter le résultat de la lecture
//retourne -1 quand c'est fini
int n = 0;
//Tant que l'affectation dans la variable est possible, on boucle.
//Lorsque le fichier est terminé, l'affectation n'est plus possible !
//Donc on sort de la boucle.
while((n = fis.read(buf)) >= 0)
{
//On écrit dans notre deuxième fichier
//avec l'objet adéquat
fos.write(buf);
//On affiche ce qu'a lu notre boucle
//au format byte et au format char
for(byte bit : buf)
System.out.print("\t" + bit + "(" + (char)bit + ")");
System.out.println("");
}
//On ferme nos flux de données
fis.close();
fos.close();
System.out.println("Copie terminée !");
} catch (FileNotFoundException e) {
//Cette exception est levée
//si l'objet FileInputStream ne trouve aucun fichier
e.printStackTrace();
} catch (IOException e) {
//Celle-ci se produit lors d'une erreur
//d'écriture ou de lecture
e.printStackTrace();
}
}
}
|
Pour que l'objet FileInputStream fonctionne, le fichier DOIT exister ! Sinon l'exception FileNotFoundException est levée.
Par contre, si vous ouvrez un flux en écriture (FileOutputStream) vers un fichier inexistant, celui-ci sera créé AUTOMATIQUEMENT !
Notez bien les imports pour pouvoir utiliser ces objets ! Mais comme vous le savez déjà, vous pouvez taper votre code et ensuite faire "control + shift + O" pour faire les imports automatiques.
À l'exécution de ce code, vous pouvez voir que le fichier "
test2.txt" a bien été créé et qu'il contient exactement la même chose que "
test.txt" !
De plus, j'ai ajouté dans la console les données que votre programme va utiliser (lecture et écriture).
Voici le résultat de ce code :
Les objets
FileInputStream et
FileOutputStream sont assez rudimentaires car ils travaillent avec un certain nombre d'octets à lire. Ceci explique pourquoi ma condition de boucle était si tordue...
Justement, tu ne pourrais pas nous expliquer un peu plus...
Mais bien sûr, je n'allais pas vous laisser comme ça...
Voici un rappel important.
Lorsque vous voyez des caractères dans un fichier ou sur votre écran, ceux-ci ne veulent pas dire grand-chose pour votre PC, car lui, il ne comprend que le binaire ! Vous savez, les suites de 0 et de 1...
Donc, afin de pouvoir afficher et travailler avec des caractères, un système d'encodage a été mis au point (qui a fort évolué d'ailleurs) !
Sachez qu'à chaque caractère que vous saisissez ou que vous lisez dans un fichier, correspond à un code binaire et ce code binaire correspond à un code décimal : voici
la table de jeu de caractères.
Cependant, au début, seuls les caractères de a-z, de A-Z et les chiffres de 0-9 (127 premiers caractères de la table du lien ci-dessus) étaient codés (codage
UNICODE 1) correspondant aux caractères se trouvant dans la langue anglaise mais, rapidement, ce codage s'est avéré trop limité pour des langues ayant des caractères accentués (français, espagnol...). Un jeu de codage étendu a été mis en place afin de pallier ce problème !
Chaque code binaire UNICODE 1 est codé sur 8 bits, soit 1 octet. Une variable de type
byte, en Java, correspond en fait à 1 octet et non 1 bit !
Les objets que nous venons d'utiliser emploient la première version d'UNICODE 1 qui ne comprend pas les caractères accentués, c'est pour cela que les caractères de ce type, dans notre fichier, ont un code décimal négatif !
Lorsque nous définissons un tableau de
byte à 8 entrées, cela signifie que nous allons lire 8 octets à la fois.
Vous pouvez voir qu'à chaque tour de boucle, notre tableau de
byte contient huit valeurs correspondant chacune à un code décimal qui, lui, correspond à un caractère (valeur entre parenthèses à côté du code décimal).
Vous pouvez voir que les codes décimaux négatifs sont inconnus car ils sont représentés par "
?" ; de plus, il y a des caractères invisibles dans notre fichier :
- les espaces : SP pour SPace, code décimal 32 ;
- les sauts de lignes : LF pour Line Feed, code décimal 13 ;
- les retours chariot : CR pour Cariage Return, code décimal 10.
Il en existe d'autres ; en fait, les 32 premiers caractères du tableau des caractères sont invisibles !
On comprend mieux...
Vous voyez que le traitement des flux suivent une logique et une syntaxe précises !
Lorsque nous avons copié notre fichier, nous avons récupéré un certain nombre d'octets dans un flux entrant que nous avons passé à un flux sortant.
À chaque tour de boucle, le flux entrant est lu en suivant tandis que le flux sortant, lui, écrit dans un fichier en suivant.
Cependant, il existe à présent des objets beaucoup plus faciles à utiliser, mais ceux-ci travaillent tout de même avec les deux objets que nous venons de voir !:D
Ces objets font aussi partie de la hiérarchie citée précédemment... Seulement, il existe une super-classe définissant ceux-ci.
Continuons avec les flux filtrés.
Ces deux classes sont en fait des classes abstraites. Elles définissent un comportement global pour ces classes filles qui, elles, permettent d'ajouter des fonctionnalités aux flux d'entrée / sortie !
Voici un diagramme de classe représentant la hiérarchie de classes :
Vous pouvez voir qu'il y a quatre classes filles héritant de
FilterInputStream (idem pour
FilterOutputStream) :
- DataInputStream : offre la possibilité de lire directement des types primitifs (double, char, int), ceci grâce à des méthodes comme readDouble()
, readInt()
...
- BufferedInputStream : cette classe permet d'avoir un tampon à disposition dans la lecture du flux. En gros, les données vont tout d'abord remplir le tampon et, dès que celui-ci est rempli, le programme a accès aux données ;
- PushbackInputStream : permet de remettre un octet déjà lu dans le flux entrant ;
- LineNumberInputStream : cette classe offre la possibilité de récupérer le numéro de ligne lue à un instant T.
Les classes dérivant de FilterOutputStream ont les mêmes fonctionnalités mais pour l'écriture !
Ces classes prennent une instance dérivant des classes
InputStream (pour les classes héritant de
FilterInputStream) ou de
OutputStream (pour les classes héritant de
FilterOutputStream)...
Vous pouvez cumuler les filtres, c'est-à-dire que, vu que ces classes acceptent une instance de leur super-classe, ils acceptent une instance de leur cousin !
Donc, vous pouvez avoir des choses du genre :
Code : Java1
2
3
4
5
6 | FileInputStream fis = new FileInputStream(new File("toto.txt"));
DataInputStream dis = new DataInputStream(fis);
BufferedInputStream bis = new BufferedInputStream(dis);
//Ou en condensé :
//BufferedInputStream bis = new BufferredInputStream(new DataInputStream(FileInputStream(new File("toto.txt"))));
|
Afin de vous rendre compte des améliorations apportées par ces classes, nous allons lire un ÉNORME fichier texte (3.6Mo) de façon conventionnelle avec l'objet vu précédemment et avec un buffer !
Pour obtenir cet énorme fichier, rendez-vous à
cette adresse. Faites un
clic droit / Enregistrer sous... et remplacez le contenu de votre fichier test.txt par le contenu de ce fichier.
Maintenant, voici un code qui permet de tester le temps d'exécution de la lecture :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 | //Package à importer afin d'utiliser l'objet File
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
//Nous déclarons nos objets en dehors du bloc try/catch
FileInputStream fis;
BufferedInputStream bis;
try {
fis = new FileInputStream(new File("test.txt"));
bis = new BufferedInputStream(new FileInputStream(new File("test.txt")));
byte[] buf = new byte[8];
//On récupère le temps du système
long startTime = System.currentTimeMillis();
//Inutile de faire des traitements dans notre boucle
while(fis.read(buf) != -1);
//On affiche le temps d'exécution
System.out.println("Temps de lecture avec FileInputStream : " + (System.currentTimeMillis() - startTime));
//On réinitialise
startTime = System.currentTimeMillis();
//Inutile de faire des traitements dans notre boucle
while(bis.read(buf) != -1);
//On réaffiche
System.out.println("Temps de lecture avec BufferedInputStream : " + (System.currentTimeMillis() - startTime));
//On ferme nos flux de données
fis.close();
bis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
|
Et le résultat est bluffant :
La différence de temps est ÉNORME ! 1.578 secondes pour la première méthode et 0.094 seconde pour la deuxième !

Vous conviendrez que l'utilisation d'un buffer permet une nette amélioration des performances de votre code !
Je vous conseille de faire le test pour l'écriture. D'ailleurs, nous allons le faire de ce pas :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 | //Package à importer afin d'utiliser l'objet File
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
//Nous déclarons nos objets en dehors du bloc try/catch
FileInputStream fis;
FileOutputStream fos;
BufferedInputStream bis;
BufferedOutputStream bos;
try {
fis = new FileInputStream(new File("test.txt"));
fos = new FileOutputStream(new File("test2.txt"));
bis = new BufferedInputStream(new FileInputStream(new File("test.txt")));
bos = new BufferedOutputStream(new FileOutputStream(new File("test3.txt")));
byte[] buf = new byte[8];
//On récupère le temps du system
long startTime = System.currentTimeMillis();
while(fis.read(buf) != -1){
fos.write(buf);
}
//On affiche le temps d'exécution
System.out.println("Temps de lecture + écriture avec FileInputStream et FileOutputStream : " + (System.currentTimeMillis() - startTime));
//On réinitialise
startTime = System.currentTimeMillis();
while(bis.read(buf) != -1){
bos.write(buf);
}
//On réaffiche
System.out.println("Temps de lecture + écriture avec BufferedInputStream et BufferedOutputStream : " + (System.currentTimeMillis() - startTime));
//On ferme nos flux de données
fis.close();
bis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
|
Là, la différence de performance devient démentiel :
7 secondes en temps normal et 0.1 seconde avec un buffer !
Si avec ça vous n'êtes pas convaincus de l'utilité des buffers !
Je ne vais pas passer en revue tous les objets cités un peu plus haut, mais vu que vous risquez d'utiliser les objets
Data(Input/Output)Stream, nous allons les aborder rapidement, puisqu'ils s'utilisent comme les objets
BufferedInputStream. Je vous ai dit plus haut que ceux-ci ont des méthodes de lecture pour chaque type primitif : cependant, il faut que le fichier soit généré par le biais d'un
DataOutputStream pour que les méthodes fonctionnent correctement.
Nous allons donc créer un fichier de toute pièce pour le lire par la suite.
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 | //Package à importer afin d'utiliser l'objet File
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
//Nous déclarons nos objets en dehors du bloc try/catch
DataInputStream dis;
DataOutputStream dos;
try {
dos = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(
new File("sdz.txt"))));
//Nous allons écrire chaque primitif
dos.writeBoolean(true);
dos.writeByte(100);
dos.writeChar('C');
dos.writeDouble(12.05);
dos.writeFloat(100.52f);
dos.writeInt(1024);
dos.writeLong(123456789654321L);
dos.writeShort(2);
dos.close();
//On récupère maintenant les données !
dis = new DataInputStream(
new BufferedInputStream(
new FileInputStream(
new File("sdz.txt"))));
System.out.println(dis.readBoolean());
System.out.println(dis.readByte());
System.out.println(dis.readChar());
System.out.println(dis.readDouble());
System.out.println(dis.readFloat());
System.out.println(dis.readInt());
System.out.println(dis.readLong());
System.out.println(dis.readShort());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
|
Et le résultat
Le code est simple et clair et concis...
Vous avez pu constater que ce type d'objet ne manque pas de fonctionnalités ! Mais vous savez, jusqu'ici, nous ne travaillions qu'avec des types primitifs, mais vous pouvez aussi travailler avec des objets !
La quoi ?
La sérialisation. C'est le nom que porte l'action de sauvegarder des objets !
Cela fait quelques temps déjà que vous utilisez des objets et, j'en suis sûr, vous auriez aimé que certains d'entre eux aient pu être réutilisés ensuite...
Le moment est venu de sauver vos objets d'une mort certaine !

Pour commencer, nous allons voir comment sérialiser un objet de notre composition.
Voici la classe avec laquelle nous allons travailler :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | //package à importer
import java.io.Serializable;
public class Game implements Serializable{
private String nom, style;
private double prix;
public Game(String nom, String style, double prix) {
this.nom = nom;
this.style = style;
this.prix = prix;
}
public String toString(){
return "Nom du jeu : " + this.nom +
"\nStyle de jeu : " + this.style +
"\nPrix du jeu : " + this.prix +
"\n";
}
}
|
Qu'est-ce que c'est que cette interface ? Tu n'as même pas implémenté de méthode !
En fait, cette interface n'a pas de méthode à redéfinir !

L'interface
Serializable est ce qu'on appelle une
interface marqueur !
Juste en implémentant cette interface dans un objet, Java sait que cet objet peut être sérialisé et j'irais même plus loin :
si vous n'implémentez pas cette interface dans vos objets, ceux-ci ne pourront pas être sérialisés !
Par contre, si une super-classe implémente l'interface Serializable, ses enfants seront considérés comme sérialisables !
Vous savez quasiment tout...
Maintenant, voilà comment vont se passer les choses :
- nous allons créer deux ou trois objets Game ;
- nous allons les sérialiser dans un fichier de notre choix ;
- nous allons ensuite les dé-sérialiser afin de pouvoir les réutiliser.
Nous allons donc pouvoir faire ceci grâce aux objets ObjectInputStream et ObjectOutputStream ?
Tout à fait !
Vous avez sûrement déjà deviné comment se servir de ces objets, mais nous allons tout de même travailler sur un exemple. D'ailleurs, le voici :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 | //Package à importer afin d'utiliser l'objet File
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Main {
public static void main(String[] args) {
//Nous déclarons nos objets en dehors du bloc try/catch
ObjectInputStream ois;
ObjectOutputStream oos;
try {
oos = new ObjectOutputStream(
new BufferedOutputStream(
new FileOutputStream(
new File("game.txt"))));
//Nous allons écrire chaque objet Game dans le fichier
oos.writeObject(new Game("Assassin Creed", "Aventure", 45.69));
oos.writeObject(new Game("Tomb Raider", "Plateforme", 23.45));
oos.writeObject(new Game("Tetris", "Stratégie", 2.50));
//NE PAS OUBLIER DE FERMER LE FLUX ! ! !
oos.close();
//On récupère maintenant les données !
ois = new ObjectInputStream(
new BufferedInputStream(
new FileInputStream(
new File("game.txt"))));
try {
System.out.println("Affichage des jeux :");
System.out.println("*************************\n");
System.out.println(((Game)ois.readObject()).toString());
System.out.println(((Game)ois.readObject()).toString());
System.out.println(((Game)ois.readObject()).toString());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
ois.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
|
La dé-sérialisation d'objet peut engendrer une ClassNotFoundException ! Pensez donc à capturer cette exception !
Et le résultat :
VICTOIRE !
Nos objets sont enregistrés et nous avons réussi à ré-utiliser ceux-ci après enregistrement !
Ce qu'il se passe est simple : les données de vos objets sont enregistrés dans le fichier ; mais que se passerait-il si notre objet
Game avait un autre objet de votre composition en son sein ?
Voyons ça tout de suite. Créez la classe
Notice comme suit :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12 | public class Notice {
private String langue ;
public Notice(){
this.langue = "Français";
}
public Notice(String lang){
this.langue = lang;
}
public String toString() {
return "\t Langue de la notice : " + this.langue + "\n";
}
}
|
Nous allons maintenant implémenter une notice par défaut dans notre objet
Game. Voici notre classe modifiée :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | import java.io.Serializable;
public class Game implements Serializable{
private String nom, style;
private double prix;
private Notice notice;
public Game(String nom, String style, double prix) {
this.nom = nom;
this.style = style;
this.prix = prix;
this.notice = new Notice();
}
public String toString(){
return "Nom du jeu : " + this.nom +
"\nStyle de jeu : " + this.style +
"\nPrix du jeu : " + this.prix +
"\n";
}
}
|
Réessayez votre code sauvegardant vos objets
Game. Et voici le résultat :
Eh oui, votre code ne compile plus ! Ceci pour une bonne raison : votre objet
Notice n'est pas sérialisable !
Une erreur de compilation est levée !
Maintenant, deux choix s'offrent à vous :
- soit vous faites en sorte de rendre votre objet sérialisable ;
- soit vous spécifiez dans votre classe Game que la variable "notice" n'a pas à être sérialisée !
Pour la première option, c'est simple, il suffit d'implémenter l'interface sérialisable dans notre classe
Notice.
Par contre, on ne voit pas comment dire qu'un attribut n'a pas à être sérialisé...
Vous allez voir que c'est très simple, il suffit de déclarer votre variable :
transient.
Comme ceci :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | import java.io.Serializable;
public class Game implements Serializable{
private String nom, style;
private double prix;
//Maintenant, cette variable ne sera pas sérialisée
//Elle sera tout bonnement ignorée ! !
private transient Notice notice;
public Game(String nom, String style, double prix) {
this.nom = nom;
this.style = style;
this.prix = prix;
this.notice = new Notice();
}
public String toString(){
return "Nom du jeu : " + this.nom +
"\nStyle de jeu : " + this.style +
"\nPrix du jeu : " + this.prix +
"\n";
}
}
|
Vous aurez sans doute remarqué que nous n'utilisons pas la variable notice dans la méthode toString()
de notre objet Game.
Si vous faites ceci, que vous sérialisez puis dé-sérialisez vos objets, vous aurez une NullPointerException à l'invocation de la dite méthode.
Eh oui ! L'objet Notice est ignoré : il n'existe donc pas !
C'est simple, n'est-ce pas ?
Pour ceux que la sérialisation XML intéresse, je vous propose d'aller faire un tour sur
le tuto de demonixis. Il est très bien fait et vous apprendrez beaucoup de choses..
Ce que je vous propose maintenant, c'est d'appliquer cela avec un cas concret : nous allons sauvegarder les zolis dessins que nous pouvons faire sur notre ardoise mazique...
Pour ceux qui n'auraient pas gardé le projet, je propose donc d'aller refaire un tour sur
le TP en question.
Vu que, dans ce projet, les dessins se font grâce à l'objet
Point, c'est celui-ci que nous allons sérialiser !
Je ne vais pas seulement vous proposer d'en sérialiser un seul, mais plusieurs et dans différents fichiers afin que vous puissiez réutiliser différents objets.
Nous allons avoir besoin d'un objet très pratique pour réussir ceci : un
JFileChooser !
Cet objet affiche une mini-fenêtre demandant à l'utilisateur de choisir l'endroit où ouvrir ou sauvegarder des données ! Il possède aussi tout une batterie de méthodes très intéressante :
- showOpenDialog()
: affiche une fenêtre d'ouverture de fichier ;
- showSaveDialog()
: ouvre une fenêtre de sauvegarde ;
- getSelectedFile()
: retourne le fichier sélectionné par l'utilisateur ;
- getSelectedFiles()
: retourne les fichiers sélectionnés par l'utilisateur. S'il y a plusieurs sélections...

- ...
Grâce à tout ceci, vous pourrez enregistrer vos objets dans des fichiers.
Vous devez savoir que l'objet JFileChooser est très laxiste en matière d'extension de fichier.
Il ne connaît rien du contenu des fichiers qu'il traite... Donc, rien ne vous empêchera de sauvegarder vos fichiers avec l'extension .exe ou encore .java et de les relire !
Afin d'avoir une cohérence dans nos fichiers de sauvegarde, nous allons spécifier à notre
JFileChooser qu'il ne devra sauvegarder et lire que des fichiers ayant une certaine extension !
Pour faire ceci, nous devons créer une classe à part entière héritant de la classe
FileFilter car celle-ci est une classe abstraite.
Cette classe permet de redéfinir deux méthodes :
- accept(File file)
: retourne vrai si le fichier est accepté ;
- getDescription()
: retourne la description de l'extension de fichier.
Voici donc la classe ZFileFilter que nous allons utiliser afin de créer des filtres pour nos fichiers :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | import java.io.File;
import javax.swing.filechooser.FileFilter;
public class ZFileFilter extends FileFilter {
private String extension = ".sdz", description = "Fichier Ardoise Mazique";
public ZFileFilter(String ext, String descrip){
this.extension = ext;
this.description = descrip;
}
public boolean accept(File file){
return (file.isDirectory() || file.getName().endsWith(this.extension));
}
public String getDescription(){
return this.extension + " - " + this.description;
}
}
|
Maintenant nous pouvons créer des filtres !
Voici les codes source que vous devriez parfaitement comprendre vu qu'il n'y a pas beaucoup de nouveautés !
Secret (cliquez pour afficher)
Point.java
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71 | import java.awt.Color;
import java.io.Serializable;
public class Point implements Serializable{
//Couleur du point
private Color color = Color.red;
//Taille
private int size = 10;
//position sur l'axe X : initialisé au dehors du conteneur
private int x = -10;
//Position sur l'axe Y : initialisé au dehors du conteneur
private int y = -10;
//Type de point
private String type = "ROND";
/**
* Constructeur par défaut
*/
public Point(){}
/**
* Constructeur avec paramètre
* @param x
* @param y
* @param size
* @param color
* @param type
*/
public Point(int x, int y, int size, Color color, String type){
this.size = size;
this.x = x;
this.y = y;
this.color = color;
this.type = type;
}
//****************************************
// ACCESSEURS
//****************************************
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
|
DrawPanel.java
J'ai rajouté des accesseurs pour la collection de points à sauvegarder !
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118 | import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import javax.swing.JPanel;
public class DrawPanel extends JPanel{
//Couleur du pointeur
private Color pointerColor = Color.red;
//Forme du pointeur
private String pointerType = "CIRCLE";
//Position X du pointeur
private int posX = -10, oldX = -10;
//Position Y du pointeur
private int posY = -10, oldY = -10;
//pour savoir si on doit dessiner ou non
private boolean erasing = true;
//Taille du pointeur
private int pointerSize = 15;
//Collection de points !
private ArrayList<Point> points = new ArrayList<Point>();
/**
* Constructeur
*/
public DrawPanel(){
this.addMouseListener(new MouseAdapter(){
public void mousePressed(MouseEvent e){
points.add(new Point(e.getX() - (pointerSize / 2), e.getY() - (pointerSize / 2), pointerSize, pointerColor, pointerType));
repaint();
}
});
this.addMouseMotionListener(new MouseMotionListener(){
public void mouseDragged(MouseEvent e) {
//On récupère les coordonnées de la souris
//et on enlève la moitié de la taille du pointeur
//pour centrer le tracé
points.add(new Point(e.getX() - (pointerSize / 2), e.getY() - (pointerSize / 2), pointerSize, pointerColor, pointerType));
repaint();
}
public void mouseMoved(MouseEvent e) {}
});
}
/**
* Vous la connaissez maintenant, celle-là ;)
*/
public void paintComponent(Graphics g) {
g.setColor(Color.white);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
//Si on doit effacer, on ne passe pas dans le else => pas de dessin
if(this.erasing){
this.erasing = false;
}
else{
//On parcourt notre collection de points
for(Point p : this.points)
{
//On récupère la couleur
g.setColor(p.getColor());
//Selon le type de point
if(p.getType().equals("SQUARE")){
g.fillRect(p.getX(), p.getY(), p.getSize(), p.getSize());
}
else{
g.fillOval(p.getX(), p.getY(), p.getSize(), p.getSize());
}
}
}
}
/**
* Efface le contenu
*/
public void erase(){
this.erasing = true;
this.points = new ArrayList<Point>();
repaint();
}
/**
* Définit la couleur du pointeur
* @param c
*/
public void setPointerColor(Color c){
this.pointerColor = c;
}
/**
* Définit la forme du pointeur
* @param str
*/
public void setPointerType(String str){
this.pointerType = str;
}
public ArrayList<Point> getPoints() {
return points;
}
public void setPoints(ArrayList<Point> points) {
this.points = points;
repaint();
}
}
|
Fenetre.java
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328 | import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.File |