Entrées/sorties et flots en Java

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.

I. Contexte d'utilisation des E/S en Java

Nous considérons, comme base de Java, la distribution JDK de Sun, à partir de la version 1.1.

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.

Changement de forme d'une information

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.

Nous avons pris l'exemple du type float, mais dans ce qui précéde on peut remplacer:
      'float' par:   'int', 'short', 'long' ou 'double'
      'Float' par:   'Integer', 'Short', 'Long' ou 'Double'

Flots d'octets et flots de caractères

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.

Des exemples d'E/S avec Java ou un autre langage.

Examinons, pour Java et d'autres langages plus anciens, comme C, C++ ... les possibilités proposées pour la programmation d'opérations d'entrée-sortie (E/S). Dans la première situation exposée, et le premier cas, Java n'est pas à son avantage; dans le deuxième cas les langages se valent. Dans les autres situations Java est nettement à son avantage.

II. Quelques classes du paquetage java.io et autres paquetages

  1. Nom des classes

    Essayons de comprendre comment sont constitués la majorité des noms de classes de flot du paquetage java.io, en présentant quelques noms.

    Voici des noms de classes qui:

    dérivent de InputStream
    BufferedInputStream, FileInputStream, ByteArrayInputStream, PipedInputStream
    dérivent de OutputStream
    BufferedOutputStream, FileOutputStream, ByteArrayOutputStream, PipedOutputStream
    dérivent de la classe Reader
    BufferedReader, FileReader, CharArrayReader, StringReader, PipedReader
    dérivent de la classe Writer
    BufferedWriter, FileWriter, CharArrayWriter, StringWriter, PipedWriter

    Remarquons que ces mots composés permettent de savoir: Le nom commence par l'origine ou la destination du flot, et continue par un ou deux mots qui précisent le sens de l'échange et l'unité d'échange (octet ou caractère).
    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
  2. Les objets standards

    On dispose avec Java de trois flots standards, qui sont des champs statiques de la classes System:
  3. Classes et méthodes liées aux fichiers

    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.

  4. Quelques besoins courants

    Indiquons quelques situations courantes où le programmeur a besoin de plusieurs classes.

  5. Classes et méthodes liées aux types de base

    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.

  6. Classes d'exception

    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
       UTFDataFormatException
    
    Lors 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.
  7. Quelques autres paquetages

    La plupart des classes mentionnées sont dans le paquetage java.io, cependant il faut signaler les paquetages et classes suivants, dont certaines méthodes fournissent des flots d'octets 'InputStream' ou 'OutputStream':

III. Exemples

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.

  1. Lecture d'un caractère au clavier

    On suppose que c'est la première fois qu'une lecture est faite au clavier, donc on convertit l'entrée clavier, qui est prédéfinie comme un flot d'octets (classe BufferedInputStream qui dérive de InputStream), en un flot tamponné de caractères. InputStreamReader flotCar = new InputStreamReader(System.in); BufferedReader flotCarTampon = new BufferedReader(flotCar); System.out.print("Une lettre: "); try { char c = (char)flotCarTampon.read(); System.out.println(" -> Caractère Lu: '"+c+'\''); } catch (Exception e) { System.out.println("erreur de lecture "+e);};

    On remarque la nécessité du bloc try-catch.

  2. Lecture d'une ligne au clavier

    Si on a déja converti l'entrée clavier en flot tamponné de caractères, les deux premières lignes sont inutiles. Remarquez la structuration du flot d'entée en lignes, respectée par la classe BufferedReader, et l'utilisation de la méthode readLine(). // Obtenir un fichier tamponné, à partier de l'entrée standard InputStreamReader flotCar = new InputStreamReader(System.in); BufferedReader flotCarTampon = new BufferedReader(flotCar); System.out.print("Nom, prénom: "); try { String ch=flotCarTampon.readLine(); System.out.println(" -> Lu: "+ch); } catch (Exception e) { System.out.println("erreur de lecture "+e); }; }
  3. Lecture d'un entier

    Si on a déja converti l'entrée clavier en flot tamponné de caractères, les deux premières lignes sont inutiles.
    Nous utilisons ici le type int.

    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.

    // Obtenir un fichier tamponné, à partier de l'entrée standard InputStreamReader flotCar = new InputStreamReader(System.in); BufferedReader flotCarTampon = new BufferedReader(flotCar); int n=Integer.MIN_VALUE; try { System.out.print("Un entier: "); // Lecture suite de caractères String ch = flotCarTampon.readLine(); // Conversion caractères -> entier n = Integer.parseInt(ch); } catch (NumberFormatException e) { System.out.println("Conversion impossible! "+e); } catch (IOException e) { System.out.println("Erreur E/S "+e); } System.out.println(" -> Entier obtenu: "+n); En exécutant ces instructions on peut réaliser que, si la valeur tapée est précédée ou suivie d'espaces, la méthode Integer.parseInt(ch) déclenche l'exception NumberFormatException; pour éviter cela, il suffit de supprimer les espaces, donc d'écrire: n = Integer.parseInt(ch.trim());
  4. Lectures, par ligne, d'un fichier texte et affichage

    L'ouverture du fichier et la création d'un flot de caractères sont faites par le constructeur de la classe FileReader.
    Dans la programmation ci-dessous, chaque ligne affichée est précédée par son numéro. String nFic="EsXmp.java"; String lig; int num=0; // num=nb.lignes lues try { FileReader fc = new FileReader(nFic); BufferedReader fct = new BufferedReader(fc); // Parcours du fichier par ligne lig = fct.readLine(); while (lig != null){ num++; // une ligne de plus System.out.println("" + num + " : " + lig); lig = fct.readLine(); } fct.close(); } catch (FileNotFoundException e) { System.out.println("Fichier non trouvé "+e); } catch (IOException e) { System.out.println("Erreur E/S "+e); };
  5. Copie d'un fichier

    Nous programmons une recopie de fichier, sans nous préoccuper de l'existence, avant exécution, d'un fichier de même nom que celui de la copie créée.

    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.

    String nFic = "U.class"; try { File unFic = new File(nFic); int taille = (int)unFic.length(); FileInputStream fol = // flot d'octets en lecture new FileInputStream(unFic); FileOutputStream foe = // fichier d'octets en écriture new FileOutputStream("copie"); byte contenu[]= new byte[taille]; fol.read(contenu); fol.close(); foe.write(contenu); foe.close(); System.out.println("\nLecture et écriture de " +taille+" octets"); } catch (FileNotFoundException e) { System.out.println("Fichier non trouvé "+e); } catch (IOException e) { System.out.println("Création impossible "+e); };
  6. Fichiers d'une arborescence, d'extension connue

    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.

    Pour notre exemple, nous créons la classe FiltreExtension qui va:

    Avant la classe, voici un exemple d'utilisation

    String ext="class"; String nomRépertoire = "D:\\roger\\app\\java\\jb\\es"; // Est-ce bien un nom de répertoire ? File rep= new File(nomRépertoire); if(! rep.isDirectory() ) { System.out.println(rep + " n'est pas un répertoire"); System.exit(1); } // Affichage, à l'écran, des fichiers d'extension ext System.out.println("Fichier d'extension "+ext+" dans " +rep.getAbsolutePath()+" et ses sous-répertoires"); FiltreExtension f = new FiltreExtension(ext); f.listeFichiers(rep);

    Et ci-dessous la classe FiltreExtension.

    class FiltreExtension implements FileFilter { private String extension; FiltreExtension(String e) { extension=e.toLowerCase(); } /** Fournit répertoires et fichiers d'extension précisée * dans le constructeur * @param f * @return true si f est un répertoire ou un fichier d'extension voulue, et false sinon */ public boolean accept(File f) { if(f == null) return false; else if(f.isDirectory()) return true; else return extension.compareTo(getExtension(f))==0; } /** Sert pour lancer la recherche récursive */ public void listeFichiers(File rep) { listeFichiers(rep,""); } /** Recherche récursive et affichage */ private void listeFichiers(File rep, String mg) { int i,nff; // nff = nb. fichiers filtrés String mg1=mg+" "; File contenu[]=rep.listFiles(this); // Parcours des Fichiers simples for( i=0,nff=0; i<contenu.length; i++) { if(contenu[i].isFile()) { nff++; if(nff==1) System.out.println(mg+contenu[i].getParent()); System.out.println(mg1+contenu[i].getName()); } } // Parcours des sous-répertoires for( i=0,nff=0; i<contenu.length; i++) if(contenu[i].isDirectory()) listeFichiers(contenu[i],mg1); } /** Fournit l'extension du nom d'un objet File * @param f * @return les caractères de l'extension ou null si f==null ou si le nom n'a pas d'extension */ private String getExtension(File f) { if(f == null) return null; String nom = f.getName(); int pp = nom.lastIndexOf('.'); // position du point if(pp>0 && pp<nom.length()-1) return nom.substring(pp+1).toLowerCase(); else return null; } } // fin class FiltreExtension
  7. Extraction, depuis un fichier *.zip, d'un fichier texte

    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); } ;

    // Ne pas oublier: import java.util.zip.* try { ZipFile zip = new ZipFile("servletssrc.zip"); // Une entrée seule nous intéresse ZipEntry item = zip.getEntry( "org/eclipse/help/internal/webapp/data/Topic.java"); // Constructions des flots InputStream flotOctet=zip.getInputStream(item); BufferedReader flotCT = new BufferedReader( new InputStreamReader(flotOctet)); // Affichage par lignes String ligne = flotCT.readLine(); while (ligne != null){ System.out.println(ligne); ligne = flotCT.readLine(); } zip.close(); } // Pour ouvrir servletssrc.zip catch(ZipException e){ System.out.println("Fichier zip "+e); } // Pour obtenir item catch(IllegalStateException e){ System.out.println("Fichier zip "+e); } // Pour obtenir flot d'octets catch (IOException e) { System.out.println("erreur sur flot "+e); } // Pour toutes les exceptions catch (Exception e) { System.out.println("erreur de lecture "+e); } ; Il ne faut toutefois pas oublier que dans le fichier *.zip, ce texte a été compressé, mais le flot obtenu par la méthode getInputStream nous fournit un mécanisme de décompression totalement transparent.

  8. Récupération d'une image sur un site

    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.

    String urlImage="http://localhost:8080/test/logo-iut.gif"; String nomCopie="copie.gif" try { // Déclarer la connexion et ouvrir un flot URL url = new URL(urlImage); InputStream flotUrl = url.openStream(); // Fichier et flot recevant la copie FileOutputStream flotCopie = new FileOutputStream(nomCopie); // Transfert octet par octet int b=flotUrl.read(); U.a_("Disponible: "+flotUrl.available()); while( flotUrl.available()>0) { flotCopie.write(b); b=flotUrl.read(); } // Fermer fichier image flotUrl.close(); flotCopie.close(); // En plus URLConnection urlCon = url.openConnection(); int tailleFicImage = urlCon.getContentLength(); U.a_("Taille image: "+tailleFicImage); } catch (MalformedURLException e) { System.out.println("erreur sur url "+e); } catch (IOException e) { System.out.println("erreur sur flot "+e); };
  9. Sauvegarde et restauration d'une structure dynamique

    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.

    // Construction d'un arbre binaire de recherche (ABR) ABR abr=new ABR("edelweiss"); // racine de l'arbre // Ajout de quelques noeuds abr.ajouter("crocus"); abr.ajouter("gentiane"); abr.ajouter("genepi"); abr.ajouter("soldanelle"); // Les noms dans l'odre fourni par l'ABR System.out.println("Ordre alphabétique: " + abr); try { String nomFicAbr="fic.abr"; // Affichage de la structure de l'arbre binaire abr.afficher("Arbre transmis vers fichier:"); abr.versFichier(nomFicAbr); // Affichage, pour vérification, de la structure ABR abrLu = ABR.depuisFichier(nomFicAbr); abrLu.afficher("Arbre récupéré du fichier:"); } catch (IOException e) { System.out.println("erreur sur fic.abr "+e); }; Voici la programmation complète de la classe ABR, qui contient la classe interne Noeud permettant de représenter un 'noeud' d'arbre binaire (avec la chaîne de caractère et ses 2 noeuds fils-gauche et fils-droit).
    Afin de pouvoir convertir les structures dynamiques en flots d'octets, ces deux classes sont déclarées   implements Serializable .

    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.

    /** Arbre binaire de recherche * <p>Mini classe pour expérimenter les transformations entre une * structure dynamique et un flot, et vice versa * <br>Restrictions: on ne gère pas d'arbre vide, (sauf pour la lecture cf. * <b>depuisFichier()</b>) et il n'y a pas de méthode pour enlever des mots. * </p> * @author Roger Astier * @version pour test de 'sérialisation' de structure dynamique */ import java.io.*; // Serializable, IOException, FileOutputStream, // ObjectOutputStream, ObjectInputStream, FileInputStream public class ABR implements Serializable { private Noeud racine; /** Construit la racine d'un arbre, portant la chaîne m. * @param m chaîne placée sur le noeud créé */ public ABR(String m) { racine=new Noeud(m); } /** Chaînes portées par l'ABR, dans l'ordre croissant.<br> * Exemple: 'crocus' 'edelweiss' 'genepi' 'gentiane' 'soldanelle' * @return concaténation des chaînes portées par chanque noeud */ public String toString() { return (racine==null) ? "arbre vide" : racine.motsAbr(); } /** Ajoute un mot (une chaîne), en respectant la structure A.B.R.<br> * Des appels Successifs à wb>Wajouter()</b> permettent de construire un ABR. * @param m est la chaîne de caractères à ajouter */ void ajouter(String m) { racine.ajouterAbr(m);} /** Affiche un texte et la structure d'un arbre binaire. * @param msg texte affiché avant les chaînes portées par l'arbre */ void afficher(String msg) { System.out.println(msg); racine.affPréfixe(1,'r'); } /** Copie d'un arbre sur fichier (format géré par Java - sérialisation) * @param nomFic nom du fichier * @throws IOException */ void versFichier(String nomFic) throws IOException { ObjectOutputStream flot = new ObjectOutputStream ( new FileOutputStream(nomFic)); flot.writeObject(racine); flot.close(); } // Constructeur nécessaire pour la fonction depuisFichier private ABR() { } /** Récupération d'un arbre (format géré par Java - sérialisation) * @param nomFic nom du fichier * @return l'arbre binaire de recherche lu * @throws IOException */ static ABR depuisFichier(String nomFic) throws IOException { ABR abrLu=null; ObjectInputStream flot = new ObjectInputStream ( new FileInputStream(nomFic)); try { abrLu=new ABR(); abrLu.racine = (Noeud)flot.readObject();} catch(ClassNotFoundException e){ System.out.println(e.getLocalizedMessage()); } flot.close(); return abrLu; } /** Classe auxillaire représentant un noeud d'arbre */ class Noeud implements Serializable { String info; Noeud fg, fd; Noeud(String m) { info = m; fg = fd = null; } // * Suite ordonnée des chaînes portées par l'A.B.R. String motsAbr() { return ( (fg == null) ? "" : fg.motsAbr()) // mots du fils gauche + " '" + info + "'" + ( (fd == null) ? "" : fd.motsAbr()); // mots du fils droit } // Ajoute un mot (une chaîne), en parcourant les noeuds d'après la // structure A.B.R. void ajouterAbr(String m) { if(m.compareTo(info) > 0) { if(fd==null) fd=new Noeud(m); else fd.ajouterAbr(m); } else { if(fg==null) fg=new Noeud(m); else fg.ajouterAbr(m); } } // Affiche avec une marge gauche liée à la profondeur, l'élément situé // à la racine, puis les éléments du sous-arbre gauche et ceux du sous // arbre droit void affPréfixe(int prof, char c) { System.out.print(""+prof+','+c); for(int i=0; i<prof; i++) System.out.print(" "); System.out.println(info); if(fg==null && fd==null) return; if(fg == null) affProf(prof+1,'g'); else fg.affPréfixe(prof+1,'g'); if(fd == null) affProf(prof+1,'d'); else fd.affPréfixe(prof+1,'d'); } // Afficher la profondeur et c ('g'->gauche ou 'd'->droit) private void affProf(int prof,char c) {System.out.println(""+prof+','+c);} } // fin class Noeud } // fin class ABR
  10. Autre

     
   

IV. Une classe utilitaire

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.

/** Classe utilitaire, évitant des frappes au clavier. <li> Remplacer: System.out.print par <b>U.a</b> <br>System.out.println par <b>U.a_</b> <br>Utiliser aussi <b>U.a(String message, Exception e)</b> <li> Faciliter les lectures de valeurs numériques au clavier avec les méthodes <b>int_</b>, <b>float_</b> */ import java.io.*; class U { /** Flot tamponné associé à l'entrée standard. */ private static BufferedReader clavier = new BufferedReader( new InputStreamReader(System.in)); /** Afficher le texte txt. * <p> * Méthode pour abréger la frappe au clavier, car <tt>U.a(</tt> est * plus court à écrire que <tt>System.out.print(</tt> * </p> * @param txt : texte à afficher */ static void a( String txt) {System.out.print(txt);} /** * Afficher le texte txt, et <b>passer</b> à 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. * <p>La pile d'appels n'est pas affichée.</p> * @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 <b>Float.NEGATIVE_INFINITY</b> 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 <b>Float.NEGATIVE_INFINITY</b> 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

V. Références