Ce texte présente les classes d'E/S de Java, pour aider le programmeur dans ses recherches de la 'bonne classe'. Il n'est pas question ici de présenter toutes les méthodes (plusieurs milliers) mais d'avoir un complément à la documentation du JDK de Sun, afin de l'utiliser sans y être noyé.
Accessoirement, ce texte a aidé l'auteur à comprendre la conception en classes des E/S de base de Java, qui est différente de celle présente dans le langage C++. On peut mettre en avant trois différences.
Première différence:
la vision des E/S des décennies
1960, qui prévaut dans le cas C++ (extension de C), ne
correspond pas à celle des années 1990, naissance de Java.
Prenons deux exemples traduisant ces différences entre époques. Les
lectures au clavier et affichages ou impressions bien formatées sont
faciles à programmer avec C++, médiocrement traitées par Java; nous
verrons plus loin comment peut se faire une lecture de valeur numérique
en Java.
Par contre la récupération de fichiers depuis une URL, qui est facile en
Java, est plutôt un casse-tête avec le C++ de base.
Deuxième différence: les classes C++ correspondent à une vision de
conception bas niveau des E/S; on trouve les classes de gestion des
tampons, alimentation d'une part et extraction d'autre part et les
classes traduisant l'origine ou la destination du flot (clavier/écran ou
fichier disque ou zones mémoires), avec enfin la possibilité de
formater facilement un enregistrement à l'octet près.
Par contre, les classes Java correspondent à une vision d'utilisation, de
niveau plus élevé. Le langage met à la disposition du programmeur
plusieurs types de flots correspondant à des interprétations de suites
d'octets ayant des origines ou destinations variées (flots d'octets,
caractères ... en entrée ou en sortie ...) que le programmeur peut
choisir. Pas de formatage à l'octet près, mais par contre
écriture ou lecture faciles des structures dynamiques.
Signalons une différence mineure: les conversions entre suite de caractères et types numériques sont implicites dans les E/S en C++. Par contre, en Java ces conversions sont explicitées par des méthodes des classes Short, Integer, Long, Float ou Double, et elles sont absentes de toutes les classes relatives aux entrées/sorties.
Dans le langage Java un caractère (type char) est codé (codage unicode) sur 2 octets. Cette différence avec la plupart des autres langages, a pour conséquence de distinguer un octet, type byte, d'un caractère.
Ajoutons que pour palier les différences de vitesse des opérations d'E/S et des opérations en mémoire centrale, l'utilisation de zones tampon (ou buffer), est nécessaire, et que des classes Java prennent en charge cette technique, de façon quasi-transparente pour le programmeur.
Dans une opération d'entrée, par exemple lecture au clavier d'une valeur entière, il y a changement de forme de la représentation. Prenons le cas de lecture de 345: au clavier on frappe et on transmet les trois caractères '3', '4' et '5'; dans un programme en exécution, l'information numérique est par exemple stockée dans une variable de type int sous forme de 4 octets ou 32 bits, qui représentent en binaire la valeur numérique 345.
En résumé sont présents: des octets (éléments basiques), des caractères ou des suites de caractères, première interprétation sur des octets, et un codage numérique, deuxième interprétation sur des octets.
Rappelons, sur des exemples, les possibilités de conversion entre chaines de caractères et valeurs numériques.
Pour Java, dans un canal de communication, appelé flot, circulent tout d'abord des octets. Ce flot peut être soit en entrée (ou lecture), soit en sortie (ou écriture), respectivement représentés par un objet InputStream ou un objet OuptutStream.
Un flot en lecture peut s'alimenter depuis différentes sources: un
fichier texte, un fichier image, une URL, un tube, une socket ... il
sera utilisable, pour le programmeur, de la même façon quelle que
soit son origine, par un objet InputStream ou d'une classe
dérivée, qui va s'adapter à la source.
Dans l'autre sens un flot peut fournir des octets à un fichier texte,
un fichier image, un tube, une socket ... la classe
OutputStream, ou une classe dérivée, est alors
utilisée pour le représenter.
Pour faciliter l'utilisation des fichiers textes, Java introduit en outre la notion de flot de caractères, dans les classes issues de Reader pour la lecture et celles issues de Writer pour l'écriture.
Il est question, soit de lecture au clavier et affichage à l'écran, soit d'interfaces graphiques, avec des zones de saisie, des étiquettes ...
Commençons par le seul cas où Java est nettement à son désavantage.
La lecture au clavier d'une valeur numérique n'est pas facile en
Java, car le langage oblige à traiter:
La phase 1 est obligatoire, mais dans d'autres langages: la phase 2 est inexistante, car octet et caractère sont synonymes, la phase 3 est rendue transparente au programmeur et la phase 4 peut être ignorée pour simplifier les apprentissages.
Deuxième cas, affichage à l'écran: la programmation en Java est équivalente à celle d'autres langages sauf si un formatage strict est nécessaire (cadrage de texte dans un champ, de nombre de chiffres après la virgule ...) où rien n'existe dans la version standard de Java.
Pour la partie récupération d'informations depuis une interface utilisateur, Java est légèrement avantagé car les classes numériques (Integer, Float ...) proposent des méthodes de conversion de numérique en caractères et inversement, prenant en compte les cas d'impossibilité par la gestion d'exceptions, habituelle du langage.
Voici des noms de classes qui:
exemples | origine/destination | sens | octet |
---|---|---|---|
FileInputStream | File | Input | Stream |
PipedOutputStream | Piped | Output | Stream |
origine/destination | sens et caractère | ||
FileReader | File | Reader | |
PipedWriter | Piped | Writer |
Les objets de classe FileInputStream ou FileReader sont respectivement liés à des fichiers 'binaires' ou des fichiers 'texte' en lecture; FileOutputStream ou FileWriter sont les classes analogues pour les fichiers en écriture.
La classe File fournit quant à elle:
L'interface FileFilter définit une méthode permettant d'accepter ou rejeter un objet File.
Indiquons quelques situations courantes où le programmeur a besoin de plusieurs classes.
L'entrée standard, qui est un flot d'octets doit être convertie en flots de caractères, d'où les deux lignes:
InputStreamReader flotCar = new InputStreamReader(System.in); BufferedReader flotCarTampon = new BufferedReader(flotCar);Si l'objet flotCar n'est pas utilisé, on pourra écrire alors l'obtention du flot tamponné sous la forme:
BufferedReader flotCarTampon = new BufferedReader(new InputStreamReader(System.in));
Une exception peut survenir si le fichier n'existe pas; les instructions sont placées dans un bloc try-catch:
try { FileReader flotCar = new FileReader("ficTexte"); BufferedReader flotCT = new BufferedReader(flotCar); } catch (FileNotFoundException e) { System.out.println("Fichier non trouvé "+e.toString()); };
Le fichier va être créé, en effaçant éventuellement un fichier existant, du même nom. Comme une exception peut survenir (plus de place sur disque, pas le droit d'écrire ...), les instructions sont placées dans un bloc try-catch:
try { FileWriter flotCar = new FileWriter("ficTexte"); BufferedWriter flotCT = new BufferedWriter(flotCar); } catch(IOException e) { System.out.println("Fichier non créé "+e.toString()); };Ces instructions sont analogues à celles de création d'un flot de lecture d'un fichier texte.
L'interface DataOutput et les classes qui l'implémentent (par exemple DataOutputStream ou , ObjectOutputStream) représentent des flots d'octets. Elles contiennent des méthodes pour convertir les types de base en suite d'octets, suivant un format indépendant de la machine utilisée.
De façon analogue, l'interface DataInput et les classes qui l'implémentent (par exemple DataInputStream ou ObjectInputStream) représentent des flots d'octets, et contiennent des méthodes pour convertir des suites d'octets, créées par les méthodes de DataOutput, en type de base Java, sur toute machine.
Ces deux classes permettent les échanges de valeurs numériques par exemple, entre tout type de matériel. Elles ne sont pas utilisées dans le cas de programmation d'interface utilisateur, car elles ne sont pas concernées par les conversions entre suite de caractères et types de base.
Voici des classes d'exception du paquetage java.io présentées en respectant leur hiérarchie:
IOException CharConversionException EOFException() FileNotFoundException InterruptedIOException ObjectStreamException InvalidClassException InvalidObjectException NotSerializableException NotActiveException StreamCorruptedException WriteAbortedException SyncFailedException UnsupportedEncodingException UTFDataFormatExceptionLors de la programation, d'autres classes d'exception peuvent intervenir, mais elles ne font pas partie de ce paquetage; on peut noter: ClassCastException, NumberFormatException ou SecurityException.
L'ordre des exemples de programmation correspond à celui d'un apprentissage des E/S en Java: lecture d'un caractère au clavier lecture d'une ligne au clavier lecture d'un entier lectures, par ligne, d'un fichier texte et affichage copie de fichier fichiers d'une arborescence, d'extension connue extraction depuis un fichier *.zip récupération d'une image sur un site sauvegarde et restauration d'une structure dynamique.
On remarque la nécessité du bloc try-catch.
Dans le cas où, par suite d'une faute de frappe, les caractères lus ne représentent pas une valeur entière, nous décidons d'affecter la valeur Integer.MIN_VALUE, (soit -231 = -2147483648) à la variable n.
Nous remarquons que les chiffres de la valeur tapée sont lus, comme une suite de caractères dans un objet String, puis la conversion, en type int, est effectuée par une méthode statique de la classe Integer.
Nous travaillons avec un flot d'octets, et supposons que le fichier à copier est suffisamment petit (quelques méga-octets au plus) pour que son contenu soit mémorisé complètement.
Dans la programmation ci-dessous, on récupère d'abord la taille du fichier à dupliquer, afin de lire en une seule instruction tous ses octets (cf. tableau contenu); puis on écrit, également en une seule instruction, tous ces octets.
Comme des exceptions peuvent être déclenchées (fichier à lire introuvable, ou non accessible en lecture, pas d'autorisation de créer la copie ...), il est nécessaire d'ouvrir les blocs try-catch.
Nous programmons l'affichage des fichiers ayant l'extension class , situés dans l'arborescence en dessous du répertoire D:\roger\app\java\jb\es.
Les classes File et FileFilter servent à repérer les
entrées d'un répertoire (classe File) qui sont un
sous-répertoire ou un nom ayant l'extension 'class'; cette
limitation est un 'filtre' sur les entrées du répertoire, précisé en
Java, par l'interface FileFilter.
Pour un filtre prenant en compte plusieurs extensions, voir
l'exemple de la
classe demo/jfc/FileChooser.java dans la distribution JDK de Sun.
Avant la classe, voici un exemple d'utilisation
Et ci-dessous la classe FiltreExtension.
Dans le fichier servletssrc.zip il y a plusieurs sources Java,
sous forme compressée; on veut lire l'original relatif à
org/eclipse/help/internal/webapp/data/Topic.java
On utilise les classes ZipFile et ZipEntry, du paquetage
java.util.zip.
Après avoir ouvert le fichier servletssrc.zip, et extrait les
informations utiles sur le fichier texte qui nous intéresse (ci-dessous
objet item de classe ZipEntry), on demande un flot
d'octets sur ce fichier par la méthode getInputStream de la
classe ZipFile. Ensuite, de façon habituelle, on constuit un
flot tamponné de caractères pour 'lire' ce fichier texte, par lignes.
Dans la programmation ci-dessous, on a détaillé certaines exceptions,
mais ce n'est pas indispensable; la seule ligne à retenir est:
catch (Exception e) { System.out.println("erreur de lecture "+e); } ;
L'url 'http://localhost:8080/test/logo-iut.gif' s est une image que nous voulons recopier dans le fichier de nom 'copie.gif' des notre espace de travail.
En utilisant la classe URL du paquatage java.net, nous pouvons disposer d'un flot d'octets sur la ressource lointaine, et effectuer la copie avec autant de facilité que si le fichier image est sur un disque local.
Cet exemple illustre les possibilités offertes par Java pour sauvegarder les objets créés, même s'ils correspondent à des structures dynamiques créées par le programmeur; l'expression sérialisation des objets traduit les conversions bi-directionnelles entre des objets en mémoire et une suite d'octets, pouvant être écrite sur fichier ou lue depuis un fichier, de façon indépendante du matériel et du système d'exploitation.
Cet exemple peut paraître long, mais les programmations de transfert sur fichier et de récupération depuis un fichier sont courtes: les méthodes versFichier() et depuisFichier() de la classe ABR ont chacune moins de 10 instructions. C'est la création d'une structure dynamique, ici un arbre binaire de recherche, qui est responsable de la longueur.
Dans un premier temps ou présente l'utilisation de la classe ABR:
construction d'un arbre binaire de recherche, écriture sur
fichier et lecture de ce fichier pour obtenir un deuxième arbre
identique au premier.
Puis on présente la classe ABR.
Les méthodes récursives liées aux parcours d'un arbre binaire sont dans la classe Noeud; les méthodes de la classe ABR sont souvent des intermédiaires vers celles de la classe Noeud.
Mini classe pour expérimenter les transformations entre une
* structure dynamique et un flot, et vice versa
*
Restrictions: on ne gère pas d'arbre vide, (sauf pour la lecture cf.
* depuisFichier()) et il n'y a pas de méthode pour enlever des mots.
*
La classe présentée ci-dessous a été écrite, d'une part pour économiser la frappe de caractères au clavier, et d'autre part pour apprendre Java.
Un exemple pour saisir un entier au clavier:
int age=U.int_("votre age ?");
Qui peut aussi être programmé:
a("votre age ?");int age=U.int_();
en séparant l'affichage de l'invite et la saisie.
Un exemple: pour éviter d'avoir trop de
caractères à taper au clavier, on remplace:
System.out.print("Un texte "+ v);
par:
U.a("Un texte "+ v);
De même l'appel:
System.out.println("Un texte "+ v);
peut être remplacé par:
U.a_("Un texte "+ v);
Enfin la méthode:
U.a(String message, Exception e);
permet d'afficher un message lié à une exception.
Pour l'apprentissage de Java, cette classe contient des méthodes qui
facilitent dans la programmation de lectures au
clavier. Par exemple pour saisir une valeur décimale dans une variable
x de type float, il suffit d'utiliser la méthode
float_ de la classe U, et donc d'écrire:
U.a("Un décimal : "); x = U.float_();
(ne pas oublier le caractère '_' après 'float', car
'float' tout seul est un mot clé de Java, et ne peut donc pas être un
nom de méthode)
La méthode float_(String intitulé) affiche un intitulé avant
de saisir une valeur décimale.
De même il existe les méthodes int_(), int_(String intitulé), float_(), float_(String intitulé) et string_(), string_(String intitulé) pour lire au clavier, respectivement un entier, un décimal ou une chaîne de caractères.
* Méthode pour abréger la frappe au clavier, car U.a( est * plus court à écrire que System.out.print( *
* @param txt : texte à afficher */ static void a( String txt) {System.out.print(txt);} /** * Afficher le texte txt, et passer à la ligne * @param txt : texte à afficher */ static void a_( String txt) {System.out.println(txt);} /** Affiche le texte txt, puis le message lié à l'exception. *La pile d'appels n'est pas affichée.
* @param txt : texte à afficher * @param e : exception */ static void a( String txt, Exception e) { a_("\n"+txt); if( e != null) { // a_(" --> "+e.getLocalizedMessage()); a_(" --> " + e.toString()); // e.printStackTrace(); // trace des appels } } /** Affiche l'intitulé et lit une valeur numérique décimale (cette lecture est bloquante). @param intitulé : message affiché, avant la saisie @return le décimal(float) lu ou Float.NEGATIVE_INFINITY si les caractères lus ne sont pas convertibles en un 'float'. */ public static float float_(String intitulé) { U.a( intitulé ); return float_(); } /** Lit une valeur numérique décimale (lecture bloquante). @return le décimal(float) lu ou Float.NEGATIVE_INFINITY si les caractères lus ne sont pas convertibles en 'float'. */ public static float float_() { float dd=Float.NEGATIVE_INFINITY; try { String ch = clavier.readLine().trim(); while( ch.length()==0 ) ch = clavier.readLine().trim(); dd=Float.parseFloat(ch); } catch (NumberFormatException e) {U.a("float_(): (décimal attendu)",e); } catch (IOException e) {U.a("float_() (erreur E/S)",e);} return dd; } /** Affiche un intitulé et lit une valeur numérique entière (cette lecture est bloquante). @param intitulé : message affiché, avant la saisie @return l'entier lu ou Integer.MIN_VALUE si les caractères lus ne sont pas convertibles en un 'int'. */ public static int int_(String intitulé) { U.a( intitulé ); return int_(); } /** Lit une valeur numérique entière (lecture bloquante). @return l'entier lu ou Integer.MIN_VALUE si les caractères lus ne sont pas convertibles en un 'int'. */ public static int int_() { int ii=Integer.MIN_VALUE; try { String ch = clavier.readLine().trim(); // espaces enlevés while( ch.length()==0 ) ch = clavier.readLine().trim(); // Une possibilité: ii=(new Integer(ch)).intValue(); ii = Integer.parseInt(ch); } catch (NumberFormatException e) { U.a("FluxCaractère.int_(): (entier attendue!)",e); } catch (IOException e) { U.a("FluxCaractère.int_() (erreur E/S)",e); } return ii; } /** Affiche un intitulé et lit une ligne au clavier. @param intitulé : message affiché, avant la saisie @return suite de caractères lus (sans saut R/C ni saut de ligne) */ public static String string_(String intitulé) { U.a( intitulé ); return string_(); } /** Lit une ligne au clavier. @return suite de caractères lus (sans saut R/C ni saut de ligne) */ public static String string_() { String ch=null; try { ch = clavier.readLine(); } catch(IOException e) { U.a("U.string_() ", e); } return ch; } /** Arrêt de l'exécution, qui reprend en tapant R/C */ static void arrêt() { byte aux[]=new byte[80]; try { a("\nSuite >>> "); System.in.read(aux,0,80);} catch (Exception e) {} } } // fin class U