File en Java (Io)
File en Java (Io)
Je ne vous cache pas qu'il existe une foule d’objets qui ont chacun leur façon de travailler
avec les flux. Sachez que Java a décomposé les objets traitant des flux en deux catégories :
les objets travaillant avec des flux d'entrée (in), pour la lecture de flux ;
les objets travaillant avec des flux de sortie (out), pour l'écriture de flux.
Utilisation de java.io
L'objet File
Avant de commencer, créez un fichier avec l'extension que vous voulez et enregistrez-le à la
racine de votre projet Eclipse. Personnellement, je me suis fait un fichiertest.txtdont voici
le contenu :
Dans votre projet Eclipse, faites un clic droit sur le dossier de votre projet, puis New > File.
Vous pouvez nommer votre fichier ainsi qu'y taper du texte !
Le nom du dossier contenant mon projet s'appelle « IO » et mon fichier texte est à cette
adresse :D:\Mes documents\Codage\SDZ\Java-SDZ\IO\test.txt. Nous allons
maintenant voir ce dont l'objetFileest capable. Vous remarquerez que cet objet est très
simple à utiliser et que ses méthodes sont très explicites.
import java.io.File;
System.out.println(file.getAbsolutePath());
try {
int i = 1;
if((i%4) == 0){
System.out.print("\n");
i++;
System.out.println("\n");
} catch (NullPointerException e) {
}
Le résultat est bluffant (voir figure suivante) !
Test de l'objet File
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 à la racine du PC.
Vous pouvez aussi effacer le fichier grâce la méthodedelete(), créer des répertoires avec
la méthodemkdir()(le nom donné à ce répertoire ne pourra cependant pas contenir de point
(«.»)) etc.
Maintenant que vous en savez un peu plus sur cet objet, nous pouvons commencer à
travailler avec notre fichier !
Comme vous l'avez sans doute deviné, il existe une hiérarchie de classes pour les
traitementsinet une autre pour les traitementsout. Ne vous y trompez pas, les classes
héritant d'InputStreamsont destinées à la lecture et les classes héritant d'OutputStreamse
chargent de l'écriture !
Vous auriez dit le contraire ? 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 à 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, lorsqu'il va écrire dans un fichier (ou à l'écran, souvenez-vous
deSystem.out.println), par exemple, il va faire sortir des informations ; donc, pour
lui, ce flux 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 !
//Packages à 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;
try {
int n = 0;
// plus possible !
fos.write(buf);
// format char
System.out.println("");
//Ceci permet d'avoir un buffer vierge à chaque lecture et ne pas avoir de doublon en fin de
fichier
} catch (FileNotFoundException e) {
// aucun fichier
e.printStackTrace();
} catch (IOException e) {
} finally {
// que ces instructions seront exécutées dans tous les cas même si
try {
if (fis != null)
fis.close();
} catch (IOException e) {
e.printStackTrace();
try {
if (fos != null)
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
Pour que l'objetFileInputStreamfonctionne, le fichier doit exister ! Sinon
l'exceptionFileNotFoundExceptionest 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 faire ensuite CTRL + SHIFT + O pour que les
imports soient automatiques.
À l'exécution de ce code, vous pouvez voir que le fichiertest2.txta bien été créé et
qu'il contient exactement la même chose quetest.txt! De plus, j'ai ajouté dans la
console les données que votre programme va utiliser (lecture et écriture).
La figure suivante représente le résultat de ce code.
Copie de
fichier
Le blocfinallypermet de s'assurer que nos objets ont bien fermé leurs liens avec
leurs fichiers respectifs, ceci afin de permette à Java de détruire ces objets pour ainsi
libérer un peu de mémoire à votre ordinateur.
En effet, les objets utilisent des ressources de votre ordinateur que Java ne peut pas
libérer de lui-même, vous devez être sûr que la vanne est fermée ! Ainsi, même si
une exception est levée, le contenu du blocfinallysera exécuté et nos ressources
seront libérées. Par contre, pour alléger la lecture, je ne mettrai plus ces blocs
dans les codes à venir mais pensez bien à les mettre dans vos codes.
Les objetsFileInputStreametFileOutputStreamsont assez rudimentaires, car ils
travaillent avec un nombre déterminé d'octets à lire. Cela explique pourquoi ma
condition de boucle était si tordue…
Lorsque vous voyez des caractères dans un fichier ou sur votre écran, ils ne veulent
pas dire grand-chose pour votre PC, car il ne comprend que le binaire (vous savez,
les suites de 0 et de 1). Ainsi, afin de pouvoir afficher et travailler avec des
caractères, un système d'encodage (qui a d'ailleurs fort évolué) a été mis au point.
Sachez que 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.
Voyez la table de correspondance (on parle de la table ASCII).
Cependant, au début, seuls les caractères de a à z, de A à Z et les chiffres de 0 à 9 (les 127
premiers caractères de la table ASCII) étaient codés (UNICODE 1), correspondant aux
caractères se trouvant dans la langue anglaise. Mais ce codage s'est rapidement avéré trop
limité pour des langues comportant des caractères accentués (français, espagnol…). Un jeu
de codage de caractères étendu a donc é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 typebyte,
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 pourquoi ces caractères ont un code décimal
négatif dans notre fichier. Lorsque nous définissons un tableau debyteà 8 entrées, cela
signifie que nous allons lire 8 octets à la fois.
Vous pouvez voir qu'à chaque tour de boucle, notre tableau debytecontient 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 des « ? » ; de plus, il y a des caractères invisibles (les 32 premiers caractères de la table
ASCII sont invisibles !) dans notre fichier :
Il existe à présent des objets beaucoup plus faciles à utiliser, mais qui travaillent
néanmoins avec les deux objets que nous venons d'étudier. Ces objets font
également partie de la hiérarchie citée précédemment. Seulement, il existe une
superclasse qui les définit.
Les objets FilterInputStream et FilterOutputStream
Ces deux classes sont en fait des classes abstraites. Elles définissent un
comportement global pour leurs classes filles qui, elles, permettent d'ajouter des
fonctionnalités aux flux d'entrée/sortie !
La figure suivante représente un diagramme de classes schématisant leur hiérarchie.
Hiérarchie des classes du package java.io
Vous pouvez voir qu'il existe quatre classes filles héritant deFilterInputStream(de
même pourFilterOutputStream(les classes dérivant deFilterOutputStreamont les
mêmes fonctionnalités, mais en écriture)):
DataInputStream: offre la possibilité de lire directement des types primitifs
(double,char,int) grâce à des méthodes commereadDouble(),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 plein, le programme accède 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 la ligne lue à un instant T.
Ces classes prennent en paramètre une instance dérivant des classesInputStream(pour les
classes héritant deFilterInputStream) ou deOutputStream(pour les classes héritant
deFilterOutputStream).
Puisque ces classes acceptent une instance de leur superclasse en paramètre, vous
pouvez cumuler les filtres et obtenir des choses de ce genre :
FileInputStream fis = new FileInputStream(new File("toto.txt"));
new DataInputStream(
new 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,6 Mo) de façon conventionnelle avec l'objet vu
précédemment, puis grâce à un buffer.
Télécharger le fichier
Récupérez le fichier compressé grâce à un logiciel de compression/décompression
et remplacez le contenu de votre fichiertest.txtpar le contenu de ce fichier.
Maintenant, voici un code qui permet de tester le temps d'exécution de la lecture :
//Packages à 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;
FileInputStream fis;
BufferedInputStream bis;
try {
while(fis.read(buf) != -1);
//On réinitialise
startTime = System.currentTimeMillis();
while(bis.read(buf) != -1);
//On réaffiche
fis.close();
bis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Et le résultat, visible à la figure suivante, est encore une fois bluffant.
La différence de temps est vraiment énorme : 1,578 seconde 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. Faisons donc sans plus tarder le test
avec l’écriture :
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;
FileInputStream fis;
FileOutputStream fos;
BufferedInputStream bis;
BufferedOutputStream bos;
try {
while(fis.read(buf) != -1){
fos.write(buf);
}
//On réinitialise
startTime = System.currentTimeMillis();
while(bis.read(buf) != -1){
bos.write(buf);
//On réaffiche
fis.close();
bis.close();
fos.close();
bos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Là, la différence est encore plus nette, comme le montre la figure suivante.
Comparatif d'écriture avec et sans filtre
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’avoir besoin des objetsData(Input/Output)Stream, nous allons les aborder
rapidement, puisqu'ils s'utilisent comme les objetsBufferedInputStream. Je vous ai dit plus
haut que ceux-ci ont des méthodes de lecture pour chaque type primitif : il faut cependant
que le fichier soit généré par le biais d'unDataOutputStreampour que les méthodes
fonctionnent correctement.
Nous allons donc créer un fichier de toutes pièces pour le lire par la suite.
//Packages à 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;
DataInputStream dis;
DataOutputStream dos;
try {
new BufferedOutputStream(
new FileOutputStream(
new File("sdz.txt"))));
//Nous allons écrire chaque type 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();
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();
}
}
La figure suivante correspond au résultat de ce code.
Le code est simple, clair et concis. Vous avez pu constater que ce type d'objet ne manque
pas de fonctionnalités ! Jusqu'ici, nous ne travaillions qu'avec des types primitifs, mais il est
également possible de travailler avec des objets !
import java.io.Serializable;
this.nom = nom;
this.style = style;
this.prix = prix;
}
public String toString(){
}
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 Serializableest ce
qu'on appelle une « interface marqueur ». Rien qu'en implémentant cette interface
dans un objet, Java sait que cet objet peut être sérialisé. Et j'irai même plus loin : si
vous n'implémentez pas cette interface dans vos objets, ceux-ci ne pourront pas être
sérialisés ! En revanche, si une superclasse implémente l'interfaceSerializable, ses
enfants seront considérés comme sérialisables.
Voici ce que nous allons faire :
nous allons créer deux ou trois objetsGame;
nous allons les sérialiser dans un fichier de notre choix ;
nous allons ensuite les désérialiser afin de pouvoir les réutiliser.
Vous avez sûrement déjà senti comment vous allez vous servir de ces objets, mais
travaillons tout de même sur l’exemple que voici :
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;
ObjectOutputStream oos;
try {
new BufferedOutputStream(
new FileOutputStream(
new File("game.txt"))));
oos.close();
new BufferedInputStream(
new FileInputStream(
new File("game.txt"))));
try {
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'un objet peut engendrer uneClassNotFoundException, pensez
donc à la capturer !
Et voyez le résultat en figure suivante.
Sérialisation — désérialisation
Ce qu'il se passe est simple : les données de vos objets sont enregistrées dans le
fichier. Mais que se passerait-il si notre objetGameavait un autre objet de votre
composition en son sein ? Voyons ça tout de suite. Créez la classeNoticecomme suit
:
public class Notice {
public Notice(){
this.langue = "Français";
this.langue = lang;
}
public String toString() {
}
Nous allons maintenant implémenter une notice par défaut dans notre objetGame.
Voici notre classe modifiée :
import java.io.Serializable;
this.nom = nom;
this.style = style;
this.prix = prix;
}
Réessayez votre code sauvegardant vos objetsGame. La figure suivante nous montre
le résultat obtenu.
Err
eur de sérialisation
Eh non, votre code ne compile plus ! Il y a une bonne raison à cela : votre objetNoticen'est
pas sérialisable, une erreur de compilation est donc levée. Maintenant, deux choix s'offrent à
vous :
this.nom = nom;
this.style = style;
this.prix = prix;
}
Vous aurez sans doute remarqué que nous n'utilisons pas la variablenoticedans la
méthodetoString()de notre objetGame. Si vous faites ceci, que vous sérialisez puis
désérialisez vos objets, la machine virtuelle vous renverra
l’exceptionNullPointerExceptionà l'invocation de ladite méthode. Eh oui !
L'objetNoticeest ignoré : il n'existe donc pas !
Les objets CharArray(Writer/Reader)et String(Writer/Reader)
Nous allons utiliser des objets :
CharArray(Writer/Reader);
String(Writer/Reader).
Ces deux types jouent quasiment le même rôle. De plus, ils ont les mêmes méthodes
que leur classe mère. Ces deux objets n'ajoutent donc aucune nouvelle fonctionnalité
à leur objet mère.
Leur principale fonction est de permettre d'écrire un flux de caractères dans un buffer
adaptatif : un emplacement en mémoire qui peut changer de taille selon les besoins
(nous n'en avons pas parlé dans le chapitre précédent afin de ne pas l'alourdir, mais
il existe des classes remplissant le même rôle que ces classes-
ci :ByteArray(Input/Output)Stream).
Commençons par un exemple commenté des objetsCharArray(Writer/Reader):
//Packages à importer afin d'utiliser l'objet File
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.IOException;
CharArrayReader car;
try {
caw.close();
int i;
str += (char) i;
System.out.println(str);
} catch (IOException e) {
e.printStackTrace();
}
Je vous laisse le soin d'examiner ce code ainsi que son effet. Il est assez commenté
pour que vous en compreniez toutes les subtilités.
L'objetString(Writer/Reader)fonctionne de la même façon :
//Packages à importer afin d'utiliser l'objet File
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
try {
System.out.println(sw);
sw.close();
sr = new StringReader(sw.toString());
int i ;
str += (char) i;
System.out.println(str);
} catch (IOException e) {
e.printStackTrace();
}
En fait, il s'agit du même code, mais avec des objets différents ! Vous savez à
présent comment écrire un flux de texte dans un tampon de mémoire. Je vous
propose maintenant de voir comment traiter les fichiers de texte avec des flux de
caractères.
Les classes File(Writer/Reader)et Print(Writer/Reader)
Comme nous l'avons vu, les objets travaillant avec des flux utilisent des flux binaires.
La conséquence est que même si vous ne mettez que des caractères dans un fichier et que
vous le sauvegardez, les objets étudiés précédemment traiteront votre fichier de la même
façon que s’il contenait des données binaires ! Ces deux objets, présents dans le
packagejava.io, servent à lire et écrire des données dans un fichier texte.
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
FileWriter fw;
FileReader fr;
try {
//Création de l'objet
fw = new FileWriter(file);
fw.write(str);
fw.close();
fr = new FileReader(file);
str = "";
int i = 0;
//Affichage
System.out.println(str);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Vous pouvez voir que l'affichage est bon et qu'un nouveau fichier (la lecture d'un
fichier inexistant entraîne l’exceptionFileNotFoundException, et l'écriture peut
entraîner uneIOException) vient de faire son apparition dans le dossier contenant
votre projet Eclipse !
Depuis le JDK 1.4, un nouveau package a vu le jour, visant à améliorer les performances
des flux, buffers, etc. traités parjava.io. En effet, vous ignorez probablement que le
package que nous explorons depuis le début existe depuis la version 1.1 du JDK. Il était
temps d'avoir une remise à niveau afin d'améliorer les résultats obtenus avec les objets
traitant les flux. C'est là que le packagejava.nioa vu le jour !
Utilisation de java.nio
Vous l'avez sûrement deviné,niosignifie « New I/O ». Comme je vous l'ai dit précédemment,
ce package a été créé afin d'améliorer les performances sur le traitement des fichiers, du
réseau et des buffers. Il permet de lire les données (nous nous intéresserons uniquement à
l'aspect fichier) d'une façon différente. Vous avez constaté que les objets du
packagejava.iotraitaient les données par octets. Les objets du packagejava.nio, eux, les
traitent par blocs de données : la lecture est donc accélérée !
Tout repose sur deux objets de ce nouveau package : les channels et les buffers. Les
channels sont en fait des flux, tout comme dans l'ancien package, mais ils sont amenés à
travailler avec un buffer dont vous définissez la taille. Pour simplifier au maximum, lorsque
vous ouvrez un flux vers un fichier avec un objetFileInputStream, vous pouvez récupérer
un canal vers ce fichier. Celui-ci, combiné à un buffer, vous permettra de lire votre fichier
encore plus vite qu'avec unBufferedInputStream!
Reprenez le gros fichier que je vous ai fait créer dans la sous-section précédente : nous
allons maintenant le relire avec ce nouveau package en comparant le buffer conventionnel et
la nouvelle façon de faire.
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
FileInputStream fis;
BufferedInputStream bis;
FileChannel fc;
try {
//Démarrage du chrono
//Lecture
while(bis.read() != -1);
//Temps d'exécution
fc = fis.getChannel();
//Démarrage du chrono
time = System.currentTimeMillis();
//Démarrage de la lecture
fc.read(bBuff);
bBuff.flip();
//Puisque nous avons utilisé un buffer de byte afin de récupérer les données
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
La figure suivante vous montre le résultat.
IntBuffer;
CharBuffer;
ShortBuffer;
ByteBuffer;
DoubleBuffer;
FloatBuffer;
LongBuffer.
Je ne l'ai pas fait durant tout le chapitre afin d'alléger un peu les codes, mais si vous
voulez être sûrs que votre flux est bien fermé, utilisez la clausefinally. Par exemple,
faites comme ceci :
//Packages à importer afin d'utiliser l'objet File
//…
ObjectInputStream ois;
ObjectOutputStream oos;
try {
} catch (FileNotFoundException e) {
} catch (IOException e) {
finally{
if(ois != null)ois.close();
if(oos != null)oos.close();
}
}
Avec l'arrivée de Java 7, quelques nouveautés ont vu le jour pour la gestion des
exceptions sur les flux. Contrairement à la gestion de la mémoire (vos variables, vos
classes, etc.) qui est déléguée au garbage collector (ramasse miette), plusieurs
types de ressources doivent être gérées manuellement. Les flux sur des fichiers en
font parti mais, d'un point de vue plus général, toutes les ressources que vous devez
fermer manuellement (les flux réseaux, les connexions à une base de données…).
Pour ce genre de flux, vous avez vu qu'il vous faut déclarer une variable en dehors
d'un bloctry{…}catch{…}afin qu'elle soit accessible dans les autres blocs
d'instructions, le blocfinallypar exemple.
Java 7 initie ce qu'on appelle vulgairement le « try-with-resources ». Ceci vous
permet de déclarer les ressources utilisées directement dans le bloctry(…), ces
dernières seront automatiquement fermées à la fin du bloc d'instructions ! Ainsi, si
nous reprenons notre code de début de chapitre qui copie notre
fichiertest.txtverstest2.txt, nous aurons ceci :
try(FileInputStream fis = new FileInputStream("test.txt");
int n = 0;
fos.write(buf);
System.out.println("");
} catch (IOException e) {
e.printStackTrace();
}
Notez bien que les différentes ressources utilisées sont séparées par un « ; » dans le
bloctry!
C'est tout de même beaucoup plus clair et plus lisible qu'avant, surtout que vous n'avez plus
à vous soucier de la fermeture dans le blocfinally. Il faut cependant prendre quelques
précautions notamment pour ce genre de déclaration :
}
Le fait d'avoir des ressources encapsulées dans d'autres ne rend pas « visible » les
ressources encapsulées. Dans le cas précédent, si une exception est levée, le flux
correspondant à l'objetFileInputStreamne sera pas fermé. Pour pallier ce problème il suffit
de bien découper toutes les ressources à utiliser, comme ceci :
//…
}
Eh ! Avant tu utilisais l'objetFiledans l'instanciation de tes
objetsFileInputStreametFileOutputStream!
Rien ne vous échappe ! Si j'ai changé de façon de faire c'est parce qu'il y a une restriction
sur ce mode de fonctionnement. Pour rendre la fermeture automatique possible, les
développeurs de la plateforme Java 7 ont créé une nouvelle
interface :java.lang.AutoCloseable. Seuls les objets implémentant cette interface peuvent
être utilisés de la sorte ! Vous pouvez voir la liste des classes autorisées à cette adresse (et
vous constaterez que la classeFilen'en fait pas parti).
//Via l'objet FileSystem qui représente le système de fichier de l'OS hébergeant la JVM
System.out.println(chemin);
int i = 0;
i++;
if(i%4 == 0)System.out.println("\n");
} catch (IOException e) {
e.printStackTrace();
}
Vous avez également la possibilité d'ajouter un filtre à votre listing de répertoire afin
qu'il ne liste que certains fichiers :
try(DirectoryStream<Path> listing = Files.newDirectoryStream(chemin, "*.txt")){ … }
try {
try {
//Ouverture en lecture :
//Ouverture en écriture :
Files.deleteIfExists( zipFS.getPath("test.txt") );
Files.write(path, message.getBytes());
System.out.println(entry);
Files.copy(Paths.get("fichierSurDisque.txt"), zipFS.getPath("fichierDansZIP.txt"));
}
Il est également possible d'être averti via l'objetWatchServicelorsqu'un un fichier est
modifié, de gérer des entrées/sorties asynchrones via les
objetsAsynchronousFileChannel,AsynchronousSocketChannelouAsynchronousServerS
ocketChannel. Ceci permet de faire les actions en tâche de fond, sans bloquer le
code pendant l'exécution. Il est aussi possible d'avoir accès aux attributs grâce à 6
vues permettant de voir plus ou moins d'informations, à savoir :
BasicFileAttributeViewpermet un accès aux propriétés généralement
communes à tous les systèmes de fichiers ;
DosFileAttributeViewajoute le support des attributs MS-DOS
(readonly,hidden,system,archive) à l'objet ci-dessus ;
PosixFileAttributeViewajoute les permissions POSIX du monde Unix au
premier objet cité ;
FileOwnerAttributeViewpermet de manipuler le propriétaire du fichier ;
AclFileAttributeViewpermet de manipuler les droits d'accès au fichier ;
UserDefinedFileAttributeView: permet de définir des attributs personnalisés.
Le pattern decorator
Vous avez pu remarquer que les objets de ce chapitre utilisent des instances d'objets
de même supertype dans leur constructeur. Rappelez-vous cette syntaxe :
DataInputStream dis = new DataInputStream(
new BufferedInputStream(
new FileInputStream(
new File("sdz.txt"))));
La raison d'agir de la sorte est simple : c'est pour ajouter de façon dynamique des
fonctionnalités à un objet. En fait, dites-vous qu'au moment de récupérer les données de
notre objetDataInputStream, celles-ci vont d'abord transiter par les objets passés en
paramètre. Ce mode de fonctionnement suit une certaine structure et une certaine hiérarchie
de classes : c'est le pattern decorator.
Vous pouvez voir les décorateurs comme des poupées russes : il est possible de mettre une
poupée dans une autre. Cela signifie que si nous décorons notregateauavec un
objetCoucheChocolatet un objetCoucheCaramel, la situation pourrait être symbolisée par la
figure suivante.
}
Gateau.java
return "Je suis un gâteau et je suis constitué des éléments suivants. \n";
}
Couche.java
pat = p;
}
CoucheChocolat.java
public CoucheChocolat(Patisserie p) {
super(p);
}
CoucheCaramel.java
public CoucheCaramel(Patisserie p) {
super(p);
}
CoucheBiscuit.java
public CoucheBiscuit(Patisserie p) {
super(p);
}
Et voici un code de test ainsi que son résultat, représenté à la figure suivante.
public class Main{
new CoucheCaramel(
new CoucheBiscuit(
new CoucheChocolat(
new Gateau()))));
System.out.println(pat.preparer());
Résultat du test
J'ai agrémenté l'exemple d'une couche de biscuit, mais je pense que tout cela est
assez représentatif de la façon dont fonctionnent des flux d'entrée/sortie en Java.
Vous devriez réussir à saisir tout cela sans souci. Le fait est que vous commencez
maintenant à avoir en main des outils intéressants pour programmer, et c'est sans
compter les outils du langage : vous venez de mettre votre deuxième pattern de
conception dans votre mallette du programmeur.
Vous avez pu voir que l'invocation des méthodes se faisait en allant jusqu'au dernier
élément pour remonter ensuite la pile d'invocations. Pour inverser ce fonctionnement,
il vous suffit d'inverser les appels dans la méthodepreparer(): affecter d'abord le
nom de la couche et ensuite le nom du décorateur.
Les classes traitant des entrées/sorties se trouvent dans le packagejava.io.
Les classes que nous avons étudiées dans ce chapitre sont héritées des
classes suivantes :
o InputStream, pour les classes gérant les flux d'entrée ;
o OutputStream, pour les classes gérant les flux de sortie.
La façon dont on travaille avec des flux doit respecter la logique suivante :
o ouverture de flux ;
o lecture/écriture de flux ;
o fermeture de flux.
La gestion des flux peut engendrer la levée
d'exceptions :FileNotFoundException,IOExceptionetc.
L'action de sauvegarder des objets s'appelle la « sérialisation ».
Pour qu'un objet soit sérialisable, il doit implémenter l'interfaceSerializable.
Si un objet sérialisable comporte un objet d'instance non sérialisable, une
exception sera levée lorsque vous voudrez sauvegarder votre objet.
L'une des solutions consiste à rendre l'objet d'instance sérialisable, l'autre à le
déclarertransientafin qu'il soit ignoré à la sérialisation.
L'utilisation de buffers permet une nette amélioration des performances en
lecture et en écriture de fichiers.
Afin de pouvoir ajouter des fonctionnalités aux objets gérant les flux, Java
utilise le pattern « decorator ».
Ce pattern permet d'encapsuler une fonctionnalité et de l'invoquer de façon
récursive sur les objets étant composés de décorateurs.