IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java

  Chapitre 11 - Le système d’E/S de Java

pages : 1 2 3 4 5 6 7 8 9 10 

La création d'un bon système d'entrée/sortie, pour le designer du langage, est l'une des tâches les plus difficiles.

Cette difficulté est mise en évidence par le nombre d'approches différentes. Le défi semblant être dans la couverture de toutes les éventualités. Non seulement il y a de nombreuses sources et de réceptacles d'E/S avec lesquelles vous voudrez communiquer (fichiers, la console, connections réseau), mais vous voudrez converser avec elles de manières très différentes (séquentielle, accès-aléatoire, mise en mémoire tampon, binaire, caractère, par lignes, par mots, etc.).

Les designers de bibliothèque Java ont attaqué ce problème en créant de nombreuses classes. En fait, il y a tellement de classes pour le système d'E/S de Java que cela peut être intimidant au premier abord (ironiquement, le design d'E/S de Java prévient maintenant d'une explosion de classes). Il y a eu aussi un changement significatif dans la bibliothèque d'E/S après Java 1.0, quand la bibliothèque orientée-byte d'origine a été complétée par des classes d'E/S de base Unicode orientées-char. La conséquence étant qu'il vous faudra assimiler un bon nombre de classes avant de comprendre suffisamment la représentation de l'E/S Java afin de l'employer correctement. De plus, il est plutôt important de comprendre l'évolution historique de la bibliothèque E/S, même si votre première réaction est « me prenez pas la tête avec l'historique, montrez moi seulement comment l'utiliser ! » Le problème est que sans un point de vue historique vous serez rapidement perdu avec certaines des classes et lorsque vous devrez les utiliser vous ne pourrez pas et ne les utiliserez pas.

Ce chapitre vous fournira une introduction aux diverses classes d'E/S que comprend la bibliothèque standard de Java et la manière de les employer.

La classe File

Avant d'aborder les classes qui effectivement lisent et écrivent des données depuis des streams (flux), nous allons observer un utilitaire fournit avec la bibliothèque afin de vous assister lors des traitements de répertoire de fichiers.

La classe File possède un nom décevant — vous pouvez penser qu'elle se réfère a un fichier, mais pas du tout. Elle peut représenter soit le nom d'un fichier particulier ou bien les noms d'un jeu de fichiers dans un dossier. Si il s'agit d'un jeu de fichiers, vous pouvez faire appel a ce jeu avec la méthode list(), et celle-ci renverra un tableau de String. Il est de bon sens de renvoyer un tableau plutôt qu'une classe containeur plus flexible parce que le nombre d'éléments est fixé, et si vous désirez le listing d'un répertoire différent vous créez simplement un autre objet File. En fait, « CheminDeFichier ou FilePath » aurait été un meilleur nom pour cette classe. Cette partie montre un exemple d'utilisation de cette classe, incluant l'interface associée FilenameFilter.

Lister un répertoire

Supposons que vous désirez voir le listing d'un répertoire. L'objet File peut être listé de deux manières. Si vous appelez list() sans arguments, vous obtiendrez la liste complète du contenu de l'objet File . Pourtant, si vous désirez une liste restreinte — par exemple, cependant si vous voulez tous les fichiers avec une extension .java — à ce moment là vous utiliserez un « filtre de répertoire », qui est une classe montrant de quelle manière sélectionner les objets File pour la visualisation.

Voici le code de l'exemple. Notez que le résultat a été trié sans effort (par ordre alphabétique) en utilisant la méthode java.utils.Array.sort() et l'AlphabeticComparator défini au Chapitre 9 :

//: c11:DirList.java
// Affiche le listing d'un répertoire.
import java.io.*;
import java.util.*;
import com.bruceeckel.util.*;

public class DirList {
public static void main(String[] args) {
File path = new File(".");
String[] list;
if(args.length == 0)
  list = path.list();
else
  list = path.list(new DirFilter(args[0]));
Arrays.sort(list,
  new AlphabeticComparator());
for(int i = 0; i < list.length; i++)
  System.out.println(list[i]);
}
}

class DirFilter implements FilenameFilter {
String afn;
DirFilter(String afn) { this.afn = afn; }
public boolean accept(File dir, String name) {
// Information du chemin de répertoire :
String f = new File(name).getName();
return f.indexOf(afn) != -1;
}
} ///:~

La classe DirFilter « implémente » l'interface FilenameFilter. Il est utile de voir combien est simple l'interface FilenameFilter :

public interface FilenameFilter {
boolean accept(File dir, String name);
}

Cela veut dire que ce type d'objet ne s'occupe que de fournir une méthode appelée accept(). La finalité derrière la création de cette classe est de fournir la méthode accept() à la méthode list() de telle manière que list() puisse « rappeler » accept() pour déterminer quelle noms de fichiers doivent êtres inclus dans la liste. Ainsi, cette technique fait souvent référence à un rappel automatique ou parfois à un [functor] (c'est à dire, DirFilter est un functor parce que sa seule fonction est de maintenir une méthode) ou la Command Pattern (une entité, ensemble de caractéristiques, de commandes). Parce que list() prend un objet FilenameFilter comme argument, cela veut dire que l'on peut passer un objet de n'importe quelle classe implémentant FilenameFilter afin de choisir (même lors de l'exécution) comment la méthode list() devra se comporter. L'objectif d'un rappel est de fournir une flexibilité dans le comportement du code.

DirFilter montre que comme une interface ne peut contenir qu'un jeu de méthodes, vous n'êtes pas réduit a l'écriture seule de ces méthodes. (Vous devez au moins fournir les définitions pour toutes les méthodes dans une interface, de toutes les manières.) Dans ce cas, le constructeur de DirFilter est aussi créé.

La méthode accept() doit accepter un objet File représentant le répertoire où un fichier en particulier se trouve, et un String contenant le nom de ce fichier. Vous pouvez choisir d'utiliser ou ignorer l'un ou l'autre de ces arguments, mais vous utiliserez probablement au moins le nom du fichier. Rappelez vous que la méthode list() fait appel à accept() pour chacun des noms de fichier de l'objet répertoire pour voir lequel doit être inclus — ceci est indique par le résultat booléen renvoyé par accept().

Pour être sûr que l'élément avec lequel vous êtes en train de travailler est seulement le nom du fichier et qu'il ne contient pas d'information de chemin, tout ce que vous avez a faire est de prendre l'objet String et de créer un objet File en dehors de celui-ci, puis d'appeler getName(), qui éloigne toutes les informations de chemin (dans l'optique d'une indépendance vis-à-vis de la plate-forme). Puis accept() utilise la méthode indexOf() de la classe String pour voir si la chaîne de caractères recherchée afn apparaît n'importe où dans le nom du fichier. Si afn est trouvé à l'intérieur de la chaîne de caractères, la valeur retournée sera l'indice de départ d'afn, mais si il n'est pas trouvé la valeur retourné sera - 1. Gardez en tête que ce n'est qu'une simple recherche de chaîne de caractères et qui ne possède pas d'expression « globale » de comparaison d'assortiment — comme « fo?.b?r* » — qui est beaucoup plus difficile a réaliser.

La méthode list() renvoie un tableau. Vous pouvez interroger ce tableau sur sa longueur et puis vous déplacer d'un bout a l'autre de celui-ci en sélectionnant des éléments du tableau. Cette aptitude de passer facilement un tableau dedans et hors d'une méthode est une amélioration immense supérieure au comportement de C et C++.

Les classes internes anonymes

Cet exemple est idéal pour une réécriture utilisant une classe interne anonyme (décrite au Chapitre 8). Tout d'abord, une méthode filter() est créé retournant une référence à un FilenameFilter :

//: c11:DirList2.java
// Utilisation de classes internes anonymes.
import java.io.*;
import java.util.*;
import com.bruceeckel.util.*;

public class DirList2 {
public static FilenameFilter
filter(final String afn) {
// Creation de la classe anonyme interne :
return new FilenameFilter() {
  String fn = afn;
  public boolean accept(File dir, String n) {
    // Strip path information:
    String f = new File(n).getName();
    return f.indexOf(fn) != -1;
  }
}; // Fin de la classe anonyme interne.
}
public static void main(String[] args) {
File path = new File(".");
String[] list;
if(args.length == 0)
  list = path.list();
else
  list = path.list(filter(args[0]));
Arrays.sort(list,
  new AlphabeticComparator());
for(int i = 0; i < list.length; i++)
  System.out.println(list[i]);
}
} ///:~

Notez que l'argument de filter() doit être final. Ceci est requis par la classe interne anonyme pour qu'elle puisse utiliser un objet hors de sa portée.

Cette conception est une amélioration puisque la classe FilenameFilter est maintenant fortement liée à DirList2. Cependant, vous pouvez reprendre cette approche et aller plus loin en définissant la classe anonyme interne comme un argument de list(), auquel cas c'est encore plus léger :

//: c11:DirList3.java
// Construction de la classe anonyme interne «sur-place ».
import java.io.*;
import java.util.*;
import com.bruceeckel.util.*;

public class DirList3 {
public static void main(final String[] args) {
File path = new File(".");
String[] list;
if(args.length == 0)
  list = path.list();
else
  list = path.list(new FilenameFilter() {
    public boolean
    accept(File dir, String n) {
      String f = new File(n).getName();
      return f.indexOf(args[0]) != -1;
    }
  });
Arrays.sort(list,
  new AlphabeticComparator());
for(int i = 0; i < list.length; i++)
  System.out.println(list[i]);
}
} ///:~

L'argument de main() est maintenant final, puisque la classe anonyme interne utilise directement args[0].

Ceci vous montre comment les classes anonymes internes permettent la création de classes rapides-et-propres pour résoudre des problèmes. Étant donné que tout en Java tourne autour des classes, cela peut être une technique de code utile. Un avantage étant que cela garde le code permettant de résoudre un problème particulier isolé dans un même lieu. D'un autre côté, cela n'est pas toujours facile à lire, donc vous devrez l'utiliser judicieusement.

Vérification et création de répertoires

La classe File est bien plus qu'une représentation d'un fichier ou d'un répertoire existant. Vous pouvez aussi utiliser un objet File pour créer un nouveau répertoire ou un chemin complet de répertoire si ils n'existent pas. Vous pouvez également regarder les caractéristiques des fichiers (taille, dernière modification, date, lecture/écriture), voir si un objet File représente un fichier ou un répertoire, et supprimer un fichier. Ce programme montre quelques unes des méthodes disponibles avec la classe File (voir la documentation HTML à java.sun.com pour le jeu complet) :

//: c11:MakeDirectories.java
// Démonstration de l'usage de la classe File pour
// creer des répertoire et manipuler des fichiers.
import java.io.*;

public class MakeDirectories {
private final static String usage = "Usage:MakeDirectories path1 ...\n" +
"Creates each path\n" +
"Usage:MakeDirectories -d path1 ...\n" +
"Deletes each path\n" +
"Usage:MakeDirectories -r path1 path2\n" +
"Renames from path1 to path2\n";
private static void usage() {
System.err.println(usage);
System.exit(1);
}
private static void fileData(File f) {
System.out.println(
  "Absolute path: " + f.getAbsolutePath() +
  "\n Can read: " + f.canRead() +
  "\n Can write: " + f.canWrite() +
  "\n getName: " + f.getName() +
  "\n getParent: " + f.getParent() +
  "\n getPath: " + f.getPath() +
  "\n length: " + f.length() +
  "\n lastModified: " + f.lastModified());
if(f.isFile())
  System.out.println("it's a file");
else if(f.isDirectory())
  System.out.println("it's a directory");
}
public static void main(String[] args) {
if(args.length < 1) usage();
if(args[0].equals("-r")) {
  if(args.length != 3) usage();
  File
    old = new File(args[1]),
    rname = new File(args[2]);
  old.renameTo(rname);
  fileData(old);
  fileData(rname);
  return; // Sortie de main
}
int count = 0;
boolean del = false;
if(args[0].equals("-d")) {
  count++;
  del = true;
}
for( ; count < args.length; count++) {
  File f = new File(args[count]);
  if(f.exists()) {
    System.out.println(f + " exists");
    if(del) {
      System.out.println("deleting..." + f);
      f.delete();
    }
  }
  else { // N'existe pas
    if(!del) {
      f.mkdirs();
      System.out.println("created " + f);
    }
  }
  fileData(f);
}  
}
} ///:~

Dans fileData() vous pourrez voir diverses méthodes d'investigation de fichier employées pour afficher les informations sur le fichier ou sur le chemin du répertoire.

La première méthode pratiquée par main() est renameTo(), laquelle vous permet de renommer (ou déplacer) un fichier vers un nouveau chemin de répertoire signalé par l'argument, qui est un autre objet File. Ceci fonctionne également avec des répertoire de n'importe quelle longueur.

Si vous expérimentez le programme ci-dessus, vous découvrirez que vous pouvez créer un chemin de répertoire de n'importe quelle complexité puisque mkdirs() s'occupera de tout.

Entrée et sortie

Les bibliothèques d'E/S utilisent souvent l'abstraction d'un flux [stream], qui représente n'importe quelle source ou réceptacle de données comme un objet capable de produire et de recevoir des parties de données. Le flux cache les détails de ce qui arrive aux données dans le véritable dispositif d'E/S.

Les classes de la bibliothèque d'E/S Java sont divisées par entrée et sortie, comme vous pouvez le voir en regardant en ligne la hiérarchie des classes Java avec votre navigateur Web. Par héritage, toute dérivée des classes InputStream ou Reader possède des méthodes de base nommées read() pour lire un simple byte ou un tableau de bytes. De la même manière, toutes les dérivés des classes OutputStream ou Writer ont des méthodes basiques appelées write() pour écrire un seul byte ou un tableau de bytes. Cependant, de manière générale vous n'utiliserez pas ces méthodes ; elles existent afin que les autres classes puissent les utiliser — ces autres classes ayant des interfaces plus utiles. Ainsi, vous créerez rarement votre objet flux [stream] par l'emploi d'une seule classe, mais au lieu de cela en plaçant les objets ensemble sur plusieurs couches pour arriver à la fonctionnalité désirée. Le fait de créer plus d'un objet pour aboutir à un seul flux est la raison primaire qui rend la bibliothèque de flux Java confuse.

Il est utile de ranger les classes suivant leurs fonctionnalités. Pour Java 1.0, les auteurs de la bibliothèque commencèrent par décider que toutes les classes traitant de l'entrée hériteraient de l'InputStream et toutes les classes qui seraient associées avec la sortie seraient héritées depuis OutputStream.

Les types d'InputStream

Le boulot d'InputStream est de représenter les classes qui produisent l'entrée depuis différentes sources. Ces sources peuvent êtres :

  1. Une série de bytes.
  2. Un objet String.
  3. Un fichier.
  4. Un « tuyau », lequel fonctionne comme un vrai tuyau : vous introduisez des choses à une entrée et elles ressortent de l'autre.
  5. Une succession d'autres flux, que vous pouvez ainsi rassembler dans un seul flux.
  6. D'autres sources, comme une connexion internet. (Ceci sera abordé dans un prochain chapitre.)

Chacun d'entre eux possède une sous-classe associée d'InputStream. En plus, le FilterInputStream est aussi un type d'InputStream, fournissant une classe de base pour les classes de « décoration » lesquelles attachent des attributs ou des interfaces utiles aux flux d'entrée. Ceci est abordé plus tard.

Ce livre a été écrit par Bruce Eckel ( télécharger la version anglaise : Thinking in java )
Ce chapitre a été traduit par Armel Fortun ( groupe de traduction )
télécharger la version francaise (PDF) | Commandez le livre en version anglaise (amazon) | télécharger la version anglaise
pages : 1 2 3 4 5 6 7 8 9 10 
Penser en Java 2nde édition - Sommaire |  Préface |  Avant-propos | Chapitre : 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15 |  Annexe : A B C D  | Tables des matières - Thinking in Java