Entrées-sorties Aucun problème n'existe réellement. Toute chose qui existe est la solution d'un problème. Introduction Java supporte Unicode en interne. Le type 'char' désigne un caractère Unicode, et la classe 'java.lang.String' désigne une chaîne de caractères construite à partir de caractères Unicode. Java peut afficher n'importe quel caractère à travers son système de fenêtrage AWT, à condition que 1. vous positionniez la propriété système "user.language" de façon appropriée. 2. Les définitions de jeux de fontes /usr/lib/java/lib/font.properties.language soient appropriées, et 3. Le fontes spécifiées dans ce fichier soient installées. Par exemple, pour afficher du texte contenant des caractères japonais, vous devriez installer des fontes japonaise, et lancer "java -Duser.language=ja ...". Vous pouvez combiner les jeux de fontes : pour pouvoir afficher des caractères d'Europe de l'ouest, grecs et japonais simultanément, vous devriez créer une combinaison des fichiers "font.properties" (couvre ISO-8859-1), "font.properties.el" (couvre ISO-88597) et "font.properties.ja" dans un seul fichier. ??Ceci n'a pas été testé?? Les interfaces java.io.DataInput et java.io.DataOutput contiennent des méthodes appelées 'readUTF', et 'writeUTF' respectivement. Mais notez qu'elles n'utilisent pas UTF-8 ; elles utilisent un encodage UTF-8 modifié : le caractère NUL est encodé dans une séquence de deux octets 0xC0 et 0x80 à la place de 0x00, et un octet 0x00 est ajouté à la fin. Encodées de cette façon, les chaînes peuvent contenir des caractères NUL mais elles doivent néanmoins être préfixées par un champ de taille. Les fonctions C <string.h> comme strlen() et strcpy() peuvent être utilisées pour les manipuler. Pourquoi Unicode ? Les gens de différents pays utilisent différents caractères pour représenter les mots de leur langue natale. De nos jours la plupart des applications, y compris les logiciels de courrier électronique et les navigateurs, traitent correctement les caractères 8-bits. Ils peuvent donc traiter et afficher du texte correctement à condition qu'il soit représenté dans un jeu de caractères 8-bits, comme ISO8859-1. Il y a bien plus de 256 caractères dans le monde - pensez au cyrillique, à l'hébreu, à l'arabe, au chinois, au japonais au coréen et au thaï -, et de temps à autres, de nouveaux caractères sont inventés. Les problèmes que cela induit pour les utilisateurs sont les suivants : Alain Lachance. - 1 / 27 - Août 2002 Entrées-sorties Il est impossible de stocker du texte avec des jeux de caractères différents dans le même document. Par exemple, je peux citer des journaux russes dans une publication allemande ou française si j'utilise TeX, xdvi et Postscript, mais je ne peux pas le faire avec du texte pur. Tant que chaque document a son propre jeu de caractères, et que la reconnaissance des jeux de caractères n'est pas automatique, l'intervention manuelle de l'utilisateur est inévitable. Par exemple, pour voir la page d'accueil de la distribution Linux XTeamLinux http://www.xteamlinux.com.cn/, je dois dire à Netscape que la page web est codée en GB2312. De nouveaux symboles comme l'Euro sont inventés. ISO a publié un nouveau standard ISO-8859-15 qui est en gros identique à ISO-8859-1, excepté qu'il supprime des caractères rarement utilisés, comme le vieux symbole monétaire, remplacé par le signe Euro. Si les utilisateurs acceptent ce standard, ils auront des documents dans différents jeux de caractères sur leur disque, et cela deviendra une préoccupation quotidienne. Mais les ordinateurs devraient simplifier le choses, pas les compliquer. La solution à ce problème est l'adoption d'un jeu de caractères universel. Ce jeu de caractères est Unicode http://www.unicode.org/. Pour plus d'informations sur Unicode, faites man 7 unicode (page de man contenue dans le package lpdman-1.20). 1.2 Les encodages d'Unicode Cela réduit le problème de l'utilisateur (devoir jongler entre différents jeux de caractères) à un problème technique : comment transporter les caractères Unicode en utilisant des octets de 8 bits ? L'unité de 8 bits est la plus petite unité adressable de la plupart des ordinateurs et c'est aussi l'unité utilisée par les connexions réseau TCP/IP. Cependant, l'utilisation d'un octet pour la représentation d'un caractère est un accident de l'histoire dû au fait que le développement de l'ordinateur commença en Europe et aux États-Unis, où l'on pensait que 96 caractères seraient suffisants pour longtemps. Il y a fondamentalement quatre façons d'encoder des caractères Unicode dans des octets : UTF-8 128 caractères sont encodés en utilisant 1 octet : les caractères ASCII. 1920 caractères sont encodé en utilisant deux octets : le latin, le grec, le cyrillique, le copte, l'arménien, l'hébreu, les caractères arabes. 63488 caractères sont encodés en utilisant 3 octets, le chinois et le japonais entre autres. Les 2147418112 caractères restant (non encore assignés) peuvent être encodés en utilisant 4, 5 ou 6 caractères. Pour plus d'informations sur UTF-8, faites man 7 utf-8 (cette page est contenue dans le package ldpman-1.20). Alain Lachance. - 2 / 27 - Août 2002 Entrées-sorties UCS-2 Chaque caractère est représenté par deux octets. Cet encodage peut représenter seulement les 65536 premiers caractères d'Unicode. UTF-16 C'est une extension d'UTF-2 qui peut représenter 11144112 caractères Unicode. Les 65536 premiers caractères sont représentés par deux octets, les autres par quatre. UCS-4 Chaque caractère est représenté par 4 octets. L'espace nécessaire pour encoder un texte, comparativement aux encodages actuellement en usage (8 bits par caractères pour les langues européennes, plus pour le chinois/japonais/coréen), est le suivant : (Cela a une influence sur l'espace disque, et la vitesse des communications réseau. UTF-8 Pas de changement pour l'ASCII américain, juste quelques pourcents supplémentaires pour ISO-8859-1, 50 % de plus pour le chinois/japonais/coréen, 100 % de plus pour le grec et le cyrillique. UCS-2 et UTF-16 Pas de changement pour le chinois/japonais/coréen, augmentation de 100 % pour l'US ASCII et ISO-8859-1, le grec et le cyrillique. UCS-4 Augmentation de 100% pour le chinois/japonais/coréen. De 300% pour l'US ASCII et ISO-8859-1, le grec et le cyrillique. Étant donné la pénalité pour les documents américains et européens, causée par UCS-2, UTF-8 et UCS-4, il semble peu probable que ces encodages aient un potentiel pour une utilisation à grande échelle. L'API Win32 Microsoft supporte l'encodage UCS-2 depuis 1995 (au moins), cependant cet encodage n'a pas été largement adopté pour les documents -SJIS demeure prédominant au Japon. D'un autre côté UTF-8 a le potentiel pour une utilisation à large échelle, puisqu'il ne pénalise pas les utilisateurs américains et européens, et que beaucoup de logiciels de "traitement de texte" (NDT : au sens large) n'ont pas besoin d'être changés pour supporter UTF-8. Nous allons maintenant expliquer comment configurer votre système Linux pour qu'il utilise UTF-8 comme encodage de texte. Alain Lachance. - 3 / 27 - Août 2002 Entrées-sorties Java est un langage qui utilise les méthodes orientées objet modernes pour ses entrées-sorties. Une des grandes erreurs que les gens font est qu'ils confondent les entrées-sorties avec la mise en forme, les conversions et l'interprétation des données. Java sépare clairement les classes qui lisent et écrivent des octets des classes qui interprètent ces données. Souvent on a à mettre en forme des chaînes de caractères sans avoir à les écrire sur la console. Aussi on a souvent à écrire des données sans savoir ce qu'elles représentent. La séparation claire entre la mise en forme et les entrées-sorties permet la création de nouvelles classes de mise en forme sans avoir à réécrire les classes d'entréessorties du système et écrire de nouvelles classes d'entrées-sorties utilisant les classes de mise en forme existantes. Streams et Readers / Writers Java fournit deux ensembles de classes pour lire et écrire. Les "Streams" du package "java.io" servent à lire ou écrire des octets donc essentiellement de l'information dite sous forme binaire. Les "Readers / Writers" du même package servent à lire ou écrire des caractères donc de l'information dite sous forme de texte. On se souvient que les caractères Java sont des caractères Unicodes permettant de représenter à peu près n'importe quel caractère de toute langue humaine: ils sont formés de deux octets chacun. Rappelons-nous que 1 comme nombre en binaire vaudra 00000000 00000001 tandis que "1" comme caractère sera plutôt 00000000 00110001. Lire sur l'unité standard d'entrée Souvent nous avons un problème qui requiert beaucoup d'interaction avec l'usager ou encore un test simple à faire. Dans ce cas la lecture sur l'unité standard d'input est de mise. Sur les plates-formes autres que Macintosh, lorsqu'un programme commence, les trois "streams" représentant les unités standard (d'entrée de sortie et d'erreur) sont ouverts et pré-assignés à des descripteurs de fichiers prédéfinis. C'est-à-dire les variable static in, out et err de la classe System. Le résultat de cette convention est que tout programme peut utiliser System.in, System.err et System.out sans avoir à Alain Lachance. - 4 / 27 - Août 2002 Entrées-sorties ouvrir de fichier. Donc pour lire un octet de l'unité standard d'entrée (System.in) on doit écrire quelque chose comme: int x = System.in.read(); Ceci n'est évidemment pas suffisant car la lecture peut provoquer une exception. Idéalement il faut donc encadrer cet énoncé d'une structure try-catch . Cet énoncé ne nous permet pas de lire du texte (des caractères) car le contenu de la variable "x" ci-dessus a été formé à partir d'un octet lu à l'entrée: il ne correspond peut-être pas à un caractère imprimable. Afin de lire des caractères, donc tenant compte des diverses représentations de ceux-ci à travers le monde, l'usage de la classe Reader devient nécessaire. Cependant pour éviter de lire les caractères un à un, une sous-classe de Reader appelée BufferedReader est utilisée. Il y a un problème ici: l'unité standard est un "Stream" donc elle nous permet de lire des octets; mais nous voulons lire des caractères (ce ne sont pas des octets) donc nous avons besoin d'un "Reader". Un pont a été fabriqué pour résoudre ce problème. Il existe dans la classe qui s'appelle InputStreamReader. Cet InputStreamReader contient un constructeur qui permet de travailler avec un "Stream". Il transforme donc pour toutes fins pratiques un "Stream" en "Reader" que l'on passe ensuite à un BufferedReader qui permet de lire plus d'un caractère à la fois. En code Java ceci se traduit par: BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) ) Nous pourrons lire des lignes de texte à l'aide de la méthode readLine() qui retourne un String contenant les caractères Unicode d'une ligne de l'entrée. import java.io.*; /** * Lire et imprimer, à partir de System.in, vers System.out */ public class CatStdin { public static void main(String[] av) { try { BufferedReader is = new BufferedReader(new InputStreamReader(System.in)); String ligne; System.out.println("Tapez quelque chose:"); while ((ligne = is.readLine()) != null && !ligne.trim().equals("FIN")) { System.out.println(ligne); } is.close(); } catch (IOException e) { System.out.println("IOException: " + e); } } } Alain Lachance. - 5 / 27 - Août 2002 Entrées-sorties Écrire sur l'unité standard de sortie System.out est un PrintStream ce qui nous permet de générer rapidement un texte imprimable à l'aide d'énoncés simples tels que System.out.println("Vive la vie"); La méthode println() étant surchargée elle permet l'impression de tous les types de base ce qui facilite son usage. Il est possible d'utiliser un "Writer" pour écrire sur cette unité standard. Cette classe a un constructeur qui accepte un "Stream" et les mêmes méthodes que PrintStream: PrintWriter pw = new PrintWriter( System.out ); pw.println("Le poids est: " + poids ); Ouverture de fichier Il n'existe pas de méthode pour ouvrir un fichier en Java (telle que close() pour les fermer). Le fait de construire un FileReader, FileWriter, FileInputStream ou FileOutputStream correspond à l'opération d'ouverture de fichier que l'on retrouve dans les autres langages. Pour l'ouverture d'un fichier de texte il suffit de créer, dans l'ordre, un FileReader et un BufferedReader. BufferedReader inbr = new BufferedReader( new FileReader("monFichier.txt")); . . . inbr.close(); De la même façon pour ouvrir un fichier dans lequel on écrira des octets on utiliserait: BufferedOutputStream br = new BufferedOutputStream(new FileOutputStream("b.dta")); . . . br.close(); Il ne faut jamais oublier le traitement des exceptions autour de ces appels! Le code qui suit est une méthode static permettant d'ouvrir un BufferedReader si on connaît son nom. Fort des explications qui précèdent la méthode se passe de commentaires. Alain Lachance. - 6 / 27 - Août 2002 Entrées-sorties import java.io.*; /** * La méthode est static donc aucun objet à créer pour l'utiliser */ public class FileIO { /** Ouvrir un BufferedReader à partir d'un fichier nommé. */ public static BufferedReader openFile(String nomFichier) throws IOException { return new BufferedReader(new FileReader(nomFichier)); } } Lire intégralement un fichier dans une seule chaîne Cette opération est plutôt rare en Java. Un exemple d'utilisation pourrait être de lire un fichier et placer son contenu dans un TextArea. L'API standard de Java ne contient rien à ce sujet. Cet exemple permet de comprendre la notion de "Buffer" ou de mémoire tampon. L'information lue est d'abord placée dans un espace mémoire appelé tampon ou "buffer" (la variable 'b' dans le programme exemple plus bas). Lorsque le tampon est rempli, son contenu est transmis au programme. Le programme traite le contenu du tampon, ensuite il rappelle la lecture. La méthode de lecture considère alors le contenu actuel du tampon comme des détritus et écrase son contenu avec les caractères suivants du fichier. Cette méthode augmente de beaucoup l'efficacité de la lecture. Qu'arriverait-il si la lecture était faite sans tampon? Le programme devrait traiter chaque caractère lu de façon indépendante car dans ce cas Java transmettrait les caractères du fichier un par un. En effet, le traitement de fichier se fait caractère par caractère dans un Reader et octet par octet dans un Stream. (voir la section "Streams et Reader / Writer" ci-dessus) Pour utiliser ce code exemple on n'a qu'à écrire: Reader is = new FileReader("monFichier.txt"); String strFichierLu = FileIO.readerToString(is); Afin de comprendre le code on doit se rappeler premièrement que lorsqu'une lecture est faite à partir du fichier, celui-ci est prêt à transmettre l'information qui suit immédiatement dans le fichier; c'est-à-dire que les données sont lues une à une en séquence; la position de lecture dans le fichier est de plus nommée le curseur du fichier. Nous devons aussi nous rappeler qu'une chaîne de caractères (un String) est une constante en Java. Lorsqu'on concatène deux chaînes, une troisième chaîne est Alain Lachance. - 7 / 27 - Août 2002 Entrées-sorties formée contenant la concaténation désirée; les deux autres chaînes sont détruites ou ignorées selon le texte qui a été écrit. Exemple: String sa = "Allo", sb = "le monde"; sa += " la"; sa += " " + sb; À ce point-ci la variable 'sa' contient "Allo la le monde". La valeur précédente de 'sa' soit "Allo la" a été détruite et remplacée par la dernière chaîne concaténée. La variable 'sb' contient toujours sa valeur initiale tandis que les constantes "Allo", "le monde", " la" et " " demeurent accessibles dans une région statique de la mémoire afin que la prochaine exécution de la méthode contenant ces lignes se fasse correctement. Utiliser des String pour effectuer les concaténations du programme qui suit serait vraiment inefficace en temps et en mémoire. À la place de String, l'objet StringBuffer est utilisé (voir votre livre de référence). Cette classe réalise des String modifiables. Le StringBuffer nommé 'sb' dans le code ci-dessous est déclaré pour contenir initialement 2048 caractères (soit BLKSIZ / 4 ) La méthode "read" de Reader permet de lire de l'information (donc des caractères) à partir du fichier référencé par 'is'. (n = is.read(b) ) où b est un tableau pouvant contenir 8192 (BLKSIZ) caractères qui joue le rôle de notre tampon (ou buffer) d'entrée-sortie. Les caractères lus sont placés dans 'b' tandis que le nombre de caractères lus est retourné par la fonction et placé dans la variable 'n'; s'il n'y a plus de caractères à lire, la fonction retourne 0. Remarquez le code: while( (n = is.read( b ) ) > 0 ){ . . . }. Il signifie en français "À partir du fichier is, lire jusqu'à 8192 caractères, les placer dans b et retourner le nombre de caractères lus dans 'n'. Tant que 'n' est plus grand que 0 exécuter une itération". Dans la boucle, le code sb.append(b, 0, n) concatène les n caractères lus à partir de celui d'indice 0 placés dans le tableau 'b' (d'où le b, 0, n ) à la fin de sb. ( d'où le sb.append (…) ) import java.io.*; /** * Toutes les méthodes sont static donc aucun objet à créer pour les utiliser */ Alain Lachance. - 8 / 27 - Août 2002 Entrées-sorties public class FileIO { /** La dimension du tampon à utiliser */ protected static final int BLKSIZ = 8192; /** Lire un fichier texte intégralement dans un String via un Reader Le Reader doit être ouvert lors de l'appel. */ /* Lors de la lecture se souvenir que 'b' est le tampon d'entrée / sortie 'sb' est un StringBuffer ne servant qu'à optimiser l'usage des String */ public static String readerToString(Reader is) throws IOException { StringBuffer sb = new StringBuffer(BLKSIZ/4); // afin d'optimiser l'usage // des String char[] b = new char[BLKSIZ]; // b est le buffer de lecture // ne pas confondre ce buffer avec // sb le StringBuffer int n; // nombre de caractères contenus dans le buffer // Lire un bloc. S'il contient des caractères, les concaténer. while ((n = is.read(b)) > 0) { sb.append(b, 0, n); // } // Construire l'objet String une seule fois ici à partir du StringBuffer. return sb.toString(); } /** Lire un fichier texte intégralement dans un String via un Stream Le Stream doit être ouvert lors de l'appel. */ public static String inputStreamToString(InputStream is) throws IOException { return readerToString(new InputStreamReader(is)); } } Exemple de séquence de code permettant de vérifier le code ci-dessus: String ligne = FileIO.readerToString(new FileReader( TousEmploye.CHEMIN + "Employe.txt")); System.out.println(ligne); Écrire intégralement une chaîne dans un fichier Afin de comprendre le code on doit se rappeler premièrement que lorsqu'une écriture est faite dans le fichier, celui-ci est prêt à recevoir la nouvelle information à la position qui suit immédiatement la dernière écriture dans le fichier; c'est-à-dire que les données sont écrites une à une en séquence; la position d'écriture dans le fichier est de plus nommée le curseur du fichier. Alain Lachance. - 9 / 27 - Août 2002 Entrées-sorties Le code qui suit est très simple et se passe de commentaires. import java.io.*; /** * Toutes les méthodes sont static donc aucun objet à créer pour les utiliser */ public class FileIO { /** Écrire un fichier texte intégralement à partir d'un String */ public static void stringToFile(String text, String fileName) throws IOException { BufferedWriter os = new BufferedWriter(new FileWriter(fileName)); os.write(text); os.flush(); // afin de s'assurer que le tampon soit vide avant de fermer os.close(); } } Réassigner les "Stream" standard Ceci correspond à ce que les usagers appellent re-direction ou "piping". La première idée qui nous vient pour résoudre ce problème est de ré-ouvrir les unités standard. Cette méthode anti-sociale n'est pas générale et peut provoquer d'autres problèmes. // Re-direction String LOGFILENAME = "Erreurs.txt"; // Ici on ouvre un nouveau PrintStream // et on le passe à setErr System.setErr(new PrintStream(new FileOutputStream(LOGFILENAME))); System.out.println("S.V.P. Chercher les erreurs dans: " + LOGFILENAME); // Dans // dans int a[] a[1000] la suite on provoque une erreur dont la description apparaîtra le fichiers défini par LOGFILENAME = new int[5]; = 0; // On provoque un ArrayIndexOutOfBoundsException On ferait de même pour 'in' (setIn) et 'out' (setOut). Reproduire un "Stream" lors de son écriture. Au fur et à mesure que le stream est écrit, nous en voulons une copie reproduite sans avoir à modifier le programme et doubler les énoncés d'entrées-sorties. L'idée est de fabriquer une classe qui hérite de la classe PrintStream et dont les méthodes write identiques à celles de PrintStream écriront sur deux fichiers au lieu d'un seul. La sous-classe en question est baptisée TeePrintStream par analogie avec l'utilitaire 'tee' de Unix. Alain Lachance. - 10 / 27 - Août 2002 Entrées-sorties import java.io.*; /** TeePrintStream reproduit toutes les opérations de PrintStream * vers un fichier. Elle est une sous-classe de PrintStream. * Son usage est comme suit: * <PRE> * ... * TeePrintStream ts = new TeePrintStream(System.err, "err.log"); * System.setErr(ts); // Rediriger l'unité standard d'erreur * // ... code qui occasionnellement écrit sur System.err... * ts.close(); * ... * <PRE> * <P> Seuls les constructeurs et les méthodes write(), check() et close() * sont réécrites sachant que les méthodes print() ou println() doivent * passer par celles-ci. * @author Ian F. Darwin "The Java Cookbook" O'Reilly, (2001) * Merci à Svante Karlsson d'avoir aidé à formuler ceci. */ public class TeePrintStream extends PrintStream { protected PrintStream original; protected String fileName; /** Un test simple pour reproduire le log d'erreur. */ public static void main(String[] args) throws IOException { TeePrintStream ts = new TeePrintStream(System.err, "err.log"); System.setErr(ts); System.err.println("Un message d'erreur simulé."); ts.close(); } /** Construire TeePrintStream à partir d'un PrintStream ouvert, * un nom de fichier, et une boolean pour contrôler l'auto-flush. * Ceci est le constructeur majeur, les autres s'y réfèrent via this. */ public TeePrintStream(PrintStream orig, String fn, boolean flush) throws IOException { super(new FileOutputStream(fn), flush); fileName = fn; original = orig; } /* Construire TeePrintStream à partir d'un PrintStream ouvert et d'un nom de fichier. */ public TeePrintStream(PrintStream os, String fn) throws IOException { this(os, fn, true); } /** Retourner vrai si un des stream contient une erreur. */ public boolean checkError() { return original.checkError() || super.checkError(); } Alain Lachance. - 11 / 27 - Août 2002 Entrées-sorties /** Surcharger write(). Ceci est l'opération de reproduction. */ public void write(int x) { original.write(x); // "write une fois sur le stream fondamental; super.write(x); // write encore une fois sur le fichier nommé." } /** Surcharger write(). Ceci est l'opération de reproduction. */ public void write(byte[] x, int off, int len) { original.write(x, off, len); // "write sur le stream fondamental; super.write(x, off, len); // write encore sur le fichier nommé." } /** Fermer les deux streams. */ public void close() { original.close(); super.close(); } /** Flush les deux streams. */ public void flush() { original.flush(); super.flush(); } } Cette technique peut être utilisée par toute méthode requérant un PrintStream ou un OutputStream. La méthode peut être adaptée à beaucoup d'autres classes d'entrée et de sortie. Données binaires. Il s'agit ici de lire et écrire des données binaires par opposition à du texte. Pour ce faire nous utilisons DataInputStream et DataOutputStream. Les stream Java sont faits pour écrire des octets, soient des données binaires. Cependant nous voulons souvent lire et écrire des données binaires typées; dans ces cas les classes Data… sont utilisées. Ces classes sont construites pour traiter des données binaires et tous les types de base de Java. Exemple: Écrire un int et un double pour les relire ensuite. import java.io.*; Public class TestEcrireLireBinaire{ public static void main(String[] a){ int k = 50; double x = Math.PI; String NOM = "binaire.dat"; DataOutputStream dos = new DataOutputStream(new FileOutputStream(NOM)); dos.writeInt( k ); dos.writeDouble( x ); dos.close(); Alain Lachance. - 12 / 27 - Août 2002 Entrées-sorties DataInputStream dis = new DataInputStream(new FileInputStream(NOM)); k = dis.readInt(); x = dis.readDouble(); dis.close(); } } Il n'existe pas de méthode readString() ni de méthode writeString() dans les classes Data…. Pour lire ou écrire des String nous devons utiliser les méthodes readChar(), writeChar(), writeChars(), readUTF() et writeUTF(). UTF signifie "Unicode Text Format". L'usage des méthodes contenant le sigle UTF n'implique pas un transfert de deux octets par caractère transmis. Le nombre d'octets transmis varie selon le code du caractère. Les méthodes contenant le sigle Char transmettent toujours deux octets par caractère. Les méthodes disponibles pour la lecture sont: readXX() où XX est une des valeurs suivantes: Boolean, Byte, Char, Double, Float, Int, Long, Short et UTF. Les méthodes disponibles pour l'écriture sont: writeXX() où XX est une des valeurs suivantes: Boolean, Byte, Char, Double, Float, Int, Long, Short, UTF, Bytes, Chars. Les méthodes writeBytes() et writeChars() acceptent des String comme arguments, par exemple writeBytes("Allo le monde"). La méthode flush() est aussi disponible à la sortie pour vider le tampon de données. La méthode writeChar(int k) doit recevoir un int en argument. Une différence importante entre ce type de fichier et les fichiers de texte est que les fichiers dits binaires ne contiennent pas de caractère EOF car ce caractère ne pourrait pas être distingué d'une donnée binaire. Lorsqu'on conçoit des entées-sorties binaires, il est plus facile de concevoir au même moment les méthodes d'entrée et de sortie. Le code plus bas est un exemple plus complet dans lequel nous lisons séquentiellement un fichier au complet vers un tableau en mémoire. Dans le même exemple, nous écrivons séquentiellement tout le tableau dans un fichier binaire évidemment compatible avec le fichier de lecture. Il est très important de concevoir les deux méthodes d'entrée et de sortie (lireBinaire et ecrireBinaire) ensemble car l'une doit être absolument compatible avec l'autre. Toute modification à une de ces méthodes doit immédiatement être effectuée dans l'autre car les données binaires sont littérales et ne peuvent être interprétées que par le programme qui les lit et les écrit. En effet, une suite de 64 uns et zéros peut être interprétée comme deux int ou bien un long ou encore un double! Alain Lachance. - 13 / 27 - Août 2002 Entrées-sorties import java.io.*; import java.io.Serializable; public class Employe implements Serializable{ private static final int LONG_NOM = 26; private int ID; private String nom; private int age; private double salaire; public static int size(){return (4 + LONG_NOM*2 + 4 + 8); } public Employe(){ this(0, "", 0, 0); } public Employe( int leID, String leNom, int leAge, double leSalaire){ set(leID, leNom, leAge, leSalaire); } public Employe(String ligne){ set( ligne ); } public public public public int String int double getID() {return getNom() {return getAge() {return getSalaire(){return ID;} nom;} age;} salaire;} public void setID(int k) {ID = k;} public void setNom(String leNom){ if(leNom == null) leNom = ""; nom = (leNom + reproduire(' ', LONG_NOM)).substring(0, LONG_NOM); } public void setAge(int k) {age = k;} public void setSalaire(double d){salaire = d;} public void set( int leID, String leNom, int leAge, double leSalaire){ setID(leID); setNom(leNom); setAge(leAge); setSalaire(leSalaire); } public void set( String ligne ){ set(Integer.parseInt( ligne.substring(0, 6).trim()), ligne.substring(7, 33), Integer.parseInt( ligne.substring(34, 36).trim()), Double.parseDouble(ligne.substring(37)) ); } public String toString(){ String str = (ID + reproduire(' ', 6)).substring(0, 6) + " "; Alain Lachance. - 14 / 27 - Août 2002 Entrées-sorties str += (nom + reproduire(' ', LONG_NOM)).substring(0, LONG_NOM) + " "; str += (age + reproduire(' ', 2)).substring(0, 2) + " "; String sal = salaire + reproduire('0', 2); str += sal.substring(0, sal.indexOf(".")+3); return str; } String reproduire( char c, int n){ if( n <= 0 ) return ""; char table[] = new char[n]; for(int k=0; k<n; k++) table[k] = c; return new String(table); } public void ecrireBinaire( DataOutputStream dos) throws IOException { dos.writeInt(ID); dos.writeChars(completerString(nom, LONG_NOM)); //exactement LON_NOM carac dos.writeInt(age); dos.writeDouble(salaire); } public void lireBinaire( DataInputStream dis) throws IOException { ID = dis.readInt(); nom = readChars(dis, LONG_NOM); age = dis.readInt(); salaire = dis.readDouble(); } private String readChars(DataInputStream dis, int longueur) throws IOException { char[] chr = new char[longueur]; for(int k=0; k<longueur; k++) chr[k] = dis.readChar(); return new String(chr); } public void ecrireBinaire( RandomAccessFile raf) throws IOException { raf.writeInt(ID); raf.writeChars(completerString(nom,LONG_NOM)); //exactement LONG_NOM carac. raf.writeInt(age); raf.writeDouble(salaire); } public void lireBinaire( RandomAccessFile raf) throws IOException { ID = raf.readInt(); nom = readChars(raf, LONG_NOM); age = raf.readInt(); salaire = raf.readDouble(); } private String readChars(RandomAccessFile raf, int longueur) throws IOException { char[] chr = new char[longueur]; for(int k=0; k<longueur; k++) Alain Lachance. - 15 / 27 - Août 2002 Entrées-sorties chr[k] = raf.readChar(); return new String(chr); } String completerString(String str, int longueur){ if( longueur <=0 ) return str; return (str+reproduire(' ', longueur)).substring(0, longueur); } public String afficher(int k){ String str = (""+(1000+k+1)).substring(1) + " :: " + toString(); System.out.println(str); return str; } } // Fin de la classe Employe ///////////////////////////////////////////////////////////////////// import java.io.*; import java.io.Serializable; public class TousEmploye implements Serializable{ public static final String CHEMIN = "C:/College/104/TestFichiers/"; public static final int MAX_EMPLOYE = 1000; Employe vEmp[] = new Employe[MAX_EMPLOYE];; int nbvEmp = 0; public TousEmploye(String nomFichier){ lireToutFichier( nomFichier ); } void lireToutFichier( String nomFichier ){ if( nomFichier.indexOf(".txt") > 0 ) lireToutFichierTexte( nomFichier ); else if( nomFichier.indexOf(".dat") > 0 ) lireToutFichierBinaire( nomFichier ); else System.out.println("Nom illégal"); } public void afficherTous(String titre){ System.out.println("\n\n" + titre); for(int k=0; k<nbvEmp; k++){ if( k%20 == 0 ) System.out.println( "# enrg ID NOM AGE SALAIRE \n" + "====== ====== ========================== == ========="); vEmp[k].afficher( k ); } System.out.println(); } void lireToutFichierTexte( String nomFichier ){ BufferedReader br; int k; System.out.println("lireToutFichierTexte: DEBUT"); Alain Lachance. - 16 / 27 - Août 2002 Entrées-sorties try{ br = new BufferedReader(new FileReader(nomFichier)); } catch(FileNotFoundException fnfe){ System.out.println("Nom de fichier inexistant "); System.exit(1); } k = 0; try{ String ligne = br.readLine(); while( ligne != null){ vEmp[k++] = new Employe( ligne ); ligne = br.readLine(); } nbvEmp = k; br.close(); }catch(IOException ioe){ nbvEmp = k; System.out.println("Erreur d'entrée-sortie"); } System.out.println("lireToutFichierTexte: FIN"); return; } void lireToutFichierBinaire( String nomFichier ){ DataInputStream dis; int k; System.out.println("lireToutFichierBinaire: DEBUT"); try{ dis = new DataInputStream( new FileInputStream(nomFichier)); }catch(FileNotFoundException e){ System.out.println("Nom de fichier inexistant "); System.exit(1); } k = 0; try{ while( true ){ vEmp[k] = new Employe(); vEmp[k].lireBinaire(dis); k++; } // Tout code placé ici est inaccessible. }catch( EOFException e ){ nbvEmp = k; System.out.println("lireToutFichierBinaire: nbvEmp=" +nbvEmp); }catch( IOException e){ nbvEmp = k; System.out.println("Erreur d'entrée-sortie"); System.exit(2); } Alain Lachance. - 17 / 27 - Août 2002 Entrées-sorties try{ dis.close(); }catch( IOException e){ System.out.println("Erreur d'entrée-sortie"); System.exit(2) ; } System.out.println("lireToutFichierBinaire: FIN"); return; } void ecrireToutFichierBinaire( String nomFichier ){ DataOutputStream dos; int k; System.out.println("ecrireToutFichierBinaire: DEBUT"); try{ dos = new DataOutputStream( new FileOutputStream(nomFichier)); }catch(FileNotFoundException e){ System.out.println("Nom de fichier inexistant "); System.exit(1); } try{ k = 0; while( k < nbvEmp ){ vEmp[k].ecrireBinaire(dos); k++; } dos.flush(); dos.close(); }catch( IOException e){ System.out.println("Erreur d'entrée-sortie"); System.exit(2) ; } System.out.println("ecrireToutFichierBinaire: FIN"); return; } public static void main( String args[]){ TousEmploye teA = new TousEmploye( CHEMIN + "Employe.txt" ); teA.afficherTous( "Après la lecture initiale" ); teA.ecrireToutFichierBinaire( CHEMIN + "Employe.dat" ); teA = null; // Ce traitement détruit entièrement l'objet teA existant // Ce constructeur provoque la lecture du fichier binaire teA = new TousEmploye( CHEMIN + "Employe.dat" ); teA.afficherTous( "Après l'écriture et la lecture binaires" ); } } Remarquez entre autres le traitement de la fin du fichier via un EOFException lors de la lecture de tout le fichier. La condition du while étant toujours vraie, la boucle est infinie donc tout énoncé situé après l'accolade droite du while ne sera jamais exécuté. ( while(true){} inaccessible). C'est pourquoi la boucle se termine toujours Alain Lachance. - 18 / 27 - Août 2002 Entrées-sorties par l'occurrence de l'exception nommée EOFException. Le code de fin de boucle est placé dans le catch de l'exception soit: le fichier est fermé et le nombre d'enregistrements du fichier lu est mémorisé dans nbvEmp. Lorsqu'on veut lire et écrire toutes les données d'un objet la méthode la plus simple est l'usage de la sérialisation. Les classes utilisées dans ce cas sont: ObjectInputStream et ObjectOutputStream. La sérialisation La sérialisation permet la conversion d'objets en mémoire vers des objets externes et la conversion d'objets externes vers des objets en mémoire. Les résultats de ces conversions sont transmis en série un octet à la fois. Pour toutes fins pratiques, cela permet de lire et d'écrire des objets, qui peuvent être très complexes. La lecture et l'écriture des objets se fait à l'aide de ObjectInputStream et ObjectOutputStream. Ces classes permettent donc de sauvegarder des objets sur disque et aussi de transmettre des objets à travers un réseau. Lorsqu'un objet écrit via la sérialisation est composé d'un autre objet, celui-ci est aussi sérialisé. De cette façon des objets très complexes peuvent être écrits sans effort particulier de programmation autre que de déclarer tous ces objets sérialisables. Par exemple si les classes Employe et TousEmployes vues précédemment sont déclarées ainsi: import java.io.Serializable; public class Employe implements Serializable{ . . . } public class TousEmployes implements Serializable { . . . } alors, il est possible de les sérialiser c'est-à-dire de les écrire et de les lire presque sans effort comme dans l'exemple qui suit. import java.io.*; public class TestSerialize{ public static void main( String a[] ){ // Créer l'objet teA comme dans l'exemple précédent. TousEmploye teA = new TousEmploye( TousEmploye.CHEMIN + "Employe.txt" ); // Traiter l'objet teA selon le problème à résoudre. teA.afficherTous("Test de sérialisation\nAprès la lecture initiale"); // Sauvegarder l'objet teA à l'aide de la sérialisation ObjectOutputStream oos; Alain Lachance. - 19 / 27 - Août 2002 Entrées-sorties String leFichier = TousEmploye.CHEMIN + "Serie.dat"; System.out.println("Écriture par sérialisation: DEBUT"); try{ oos = new ObjectOutputStream(new FileOutputStream( leFichier )); }catch(IOException ioe){ System.out.println( "Erreur d'entrée-sortie"); return; } try{ oos.writeObject( teA ); oos.flush(); oos.close(); }catch(IOException ioe){ System.out.println( "Erreur d'entrée-sortie"); System.exit(2); } System.out.println("Écriture par sérialisation: FIN"); // Traitements divers teA = null; // Ce traitement détruit entièrement l'objet teA existant // Lire l'objet teA (conservé précédemment) à l'aide de la sérialisation. ObjectInputStream ois; System.out.println("Lecture par sérialisation: DEBUT"); try{ ois = new ObjectInputStream(new FileInputStream( leFichier )); }catch(StreamCorruptedException sce){ System.out.println( "Le fichier est corrompu."); System.exit(3); }catch(IOException ioe){ System.out.println( "Erreur d'entrée-sortie"); System.exit(2); } try{ teA = (TousEmploye)ois.readObject(); ois.close(); }catch(ClassNotFoundException ioe){ System.out.println( "Nom de fichier inexistant."); System.exit(1); }catch(IOException ioe){ System.out.println( "Erreur d'entrée-sortie"); System.exit(2); } System.out.println("Lecture par sérialisation: FIN"); teA.afficherTous("Après l'écriture et la lecture binaires"); } } Nous voyons que l'usage de la sérialisation réduit la difficulté de lire et d'écrire un objet, qui peut être très complexe, à celle de lire ou d'écrire un type de base. Ici Alain Lachance. - 20 / 27 - Août 2002 Entrées-sorties nous avons écrit d'un seul coup tout le tableau d'Employe (vEmp) ainsi que son nombre d'Employe (nbvEmp). Il est possible d'utiliser la sérialisation pour lire et écrire enregistrement par enregistrement (Employe par Employe ici). Voir votre manuel de référence pour un exemple de ce cas. L'accès direct à l'information Un fichier est considéré en Java comme une séquence d'octets. Chacun des octets étant adressable. L'adresse d'un octet du fichier est souvent appelé "la position dans le fichier". Afin de lire ou écrire à une position particulière dans un fichier nous utilisons la classe RandomAccesFile. Cette classe admet le positionnement du curseur de fichier à n'importe quelle position dans le fichier ou à la fin du fichier. Ce qui permet l'écriture de fichiers indexés et le traitement du fichier un peu comme dans une base de données. Il est difficile de se retrouver dans un fichier à accès direct si les enregistrements sont de longueurs variables. Dans ce cas la construction d'un index est une bonne solution. La création d'un tel fichier contenant des enregistrements de longueur variable est faite en écrivant les enregistrements séquentiellement. Chaque enregistrement écrit voit sa position consignée dans un index et est accompagné d'une clef permettant de le retrouver. L'index est ensuite écrit dans un fichier séparé ou bien dans le fichier qui contient les données à gérer (ce cas peut devenir alors assez complexe). Si les enregistrements sont de longueur fixe, il est facile de s'y retrouver car à l'aide d'un calcul simple la position d'un enregistrement quelconque peut être retrouvée (position = noEnregistrement * longueurEnregistrement). L'exemple plus bas utilise le fait que les enregistrements de la classe Employe sont de longueur fixe et mesurent Employe.size() (soit 68) caractères. Une façon très simple de créer un fichier d'accès direct dont les enregistrements sont de longueur fixe est de l'initialiser avec des enregistrements vides. Ensuite on écrit les enregistrements véritables à l'aide de l'accès direct (voir votre livre de référence dans ce cas). Une autre façon de le créer est possible si nous disposons d'une table d'enregistrements, souvent gardée dans un fichier texte, ordonnée sur la clef de recherche du fichier (par exemple le ID dans le cas du fichier "Employe.txt" Alain Lachance. - 21 / 27 - Août 2002 Entrées-sorties utilisé en exemple). Dans ce cas le fichier d'accès direct est créé en l'écrivant séquentiellement à partir des enregistrements de la table initiale. RandomAccessFile implémente les interfaces DataInput et DataOutput ce qui donne à cette classe toutes les propriétés des classes vues précédemment soient DataInputStream et DataOutputStream. Les principales méthodes sont: void seek(long location) qui permet le positionnement du curseur à la location désirée transmise par le paramètre location. int skipBytes( int combien) qui permet de déplacer le curseur vers l'avant du nombre d'octets spécifié par le paramètre combien. long getFilePointer() qui retourne la position du curseur. /** * Title: Tests sur les divers fichiers<p> * Description: Tests * 1- Fichiers binaires * 2- L'accès direct * Copyright: Copyright (c) Alain Lachance<p> * Company: Collège Ahuntsic<p> * @author Alain Lachance */ import java.io.*; import java.io.RandomAccessFile; public class protected protected protected protected protected TestEmployeAccesDirect { static final String NOM_FICHIER = TousEmploye.CHEMIN + "Employe.dat"; String nomFichier; RandomAccessFile raf; int premierID; int dernierID; public TestEmployeAccesDirect(String nomFichier, String mode) throws IOException{ this.nomFichier = nomFichier; raf = new RandomAccessFile(nomFichier, mode); } public long getPositionPremier()throws IOException{ Employe emp = new Employe(); raf.seek(0); long lPos = raf.getFilePointer(); emp.lireBinaire(raf); premierID = emp.getID(); return lPos; } Alain Lachance. - 22 / 27 - Août 2002 Entrées-sorties public long getPositionDernier()throws IOException{ Employe emp = new Employe(); long lPos = raf.length() - Employe.size(); if(lPos < 0 ) return lPos; raf.seek(lPos); emp.lireBinaire(raf); dernierID = emp.getID(); return lPos; } public long positionnerCurseur(int ID)throws IOException{ if(ID < premierID) throw new IOException("ID illegal. "); if(dernierID < ID) genererEnregistrements(ID - dernierID); raf.seek((long)(ID-premierID)*Employe.size()); return raf.getFilePointer(); } public void genererEnregistrements(int nombre)throws IOException{ Employe emp = new Employe(); raf.seek(raf.length()); for(; nombre>0; nombre--){ emp.setID(++dernierID); emp.ecrireBinaire(raf); } } public void afficherToutFichierReculons(String titre)throws IOException{ System.out.println("\n\n" + titre); getPositionPremier(); getPositionDernier(); Employe emp = new Employe(); int nbLignes = 0; for(int ID=dernierID; ID>=premierID; ID--){ if( nbLignes++%20 == 0 ) System.out.println( "# enrg ID NOM AGE SALAIRE \n" + "====== ====== ========================== == ========="); positionnerCurseur(ID); emp.lireBinaire(raf); emp.afficher(ID - premierID); } System.out.println(); } public static void main(String[] args) { // Création de l'objet à partir d'un fichier texte TousEmploye teA = new TousEmploye( TousEmploye.CHEMIN + "Employe.txt" ); teA.afficherTous( "Test fichiers accès direct\nAprès la lecture initiale" ); Alain Lachance. - 23 / 27 - Août 2002 Entrées-sorties teA.ecrireToutFichierBinaire( NOM_FICHIER ); teA = null; // Ce traitement détruit entièrement l'objet teA existant // À ce point-ci le fichier binaire étant créé, il est possible de l'accéder // à l'aide d'un traitement utilisant les méthodes d'accès direct Employe emp = new Employe(); try{ TestEmployeAccesDirect tad = new TestEmployeAccesDirect(NOM_FICHIER, "rw"); tad.getPositionPremier(); System.out.println("Le premier ID: " + tad.premierID); tad.getPositionDernier(); System.out.println("Le dernier ID: " + tad.dernierID); tad.positionnerCurseur(123470); emp.lireBinaire(tad.raf); emp.afficher(emp.getID() - tad.premierID); emp.setSalaire(emp.getSalaire()+500000.0); emp.setAge(emp.getAge()+10); tad.positionnerCurseur(123470); emp.ecrireBinaire(tad.raf); emp.afficher(emp.getID() - tad.premierID); tad.positionnerCurseur(123459); emp.lireBinaire(tad.raf); emp.afficher(emp.getID() - tad.premierID); tad.positionnerCurseur(123470); emp.lireBinaire(tad.raf); emp.afficher(emp.getID() - tad.premierID); tad.positionnerCurseur(tad.dernierID + 4); tad.afficherToutFichierReculons( "Fichier Accès direct affiché du dernier au premier enregistrement"); tad.positionnerCurseur(1000); }catch(IOException ioe){ System.out.println("Une exception: " + ioe.getMessage()); System.out.println( "\nCette erreur prévue d'avance ici termine cet exemple."); } } } Alain Lachance. - 24 / 27 - Août 2002 Entrées-sorties Résultats de l'exemple de l'accès direct à l'information 0----+----1----+----2----+----3----+----4----+----5 012345678901234567890123456789012345678901234567890 Fichier Employe.txt 123456 Dieudonné de la Trémoïlle 92 123456.78 123457 Dieudonné de la Trémoïlle1 82 103456.78 123458 Dieudonné de la Trémoïlle2 72 102456.78 123459 Dieudonné de la Trémoïlle3 62 101456.78 123460 Dieudonné de la Trémoïlle4 52 100456.78 123461 Dieudonné de la Trémoïlle5 42 100356.78 123462 Dieudonné de la Trémoïlle6 32 100256.78 123463 Dieudonné de la Trémoïlle7 22 100156.78 123464 Dieudonné de la Trémoïlle8 2 100056.78 123465 Dieudonné de la Trémoïlle 91 123456.78 123466 Dieudonné de la Trémoïlle1 81 103456.78 123467 Dieudonné de la Trémoïlle2 71 102456.78 123468 Dieudonné de la Trémoïlle3 61 101456.78 123469 Dieudonné de la Trémoïlle4 51 100456.78 123470 Dieudonné de la Trémoïlle5 41 100356.78 123471 Dieudonné de la Trémoïlle6 31 100256.78 123472 Dieudonné de la Trémoïlle7 21 100156.78 123473 Dieudonné de la Trémoïlle8 1 100056.78 123474 Dieudonné de la Trémoïlle 93 123456.78 123475 Dieudonné de la Trémoïlle1 83 103456.78 123476 Dieudonné de la Trémoïlle2 73 102456.78 123477 Dieudonné de la Trémoïlle3 63 101456.78 123478 Dieudonné de la Trémoïlle4 53 100456.78 123479 Dieudonné de la Trémoïlle5 43 100356.78 123480 Dieudonné de la Trémoïlle6 33 100256.78 123481 Dieudonné de la Trémoïlle7 23 100156.78 0----+----1----+----2----+----3----+----4----+----5 012345678901234567890123456789012345678901234567890 DÉBUT Résultats de la section <<--- Indices de substring() <<--- Indices de substring() <<--- Indices de substring() <<--- Indices de substring() L'accès direct à l'information lireToutFichierTexte: DEBUT lireToutFichierTexte: FIN Test fichiers accès direct Après la lecture initiale # enrg ====== 001 :: 002 :: 003 :: 004 :: 005 :: 006 :: 007 :: 008 :: 009 :: 010 :: 011 :: ID ====== 123456 123457 123458 123459 123460 123461 123462 123463 123464 123465 123466 NOM AGE ========================== == Dieudonné de la Trémoïlle 92 Dieudonné de la Trémoïlle1 82 Dieudonné de la Trémoïlle2 72 Dieudonné de la Trémoïlle3 62 Dieudonné de la Trémoïlle4 52 Dieudonné de la Trémoïlle5 42 Dieudonné de la Trémoïlle6 32 Dieudonné de la Trémoïlle7 22 Dieudonné de la Trémoïlle8 2 Dieudonné de la Trémoïlle 91 Dieudonné de la Trémoïlle1 81 Alain Lachance. - 25 / 27 - SALAIRE ========= 123456.78 103456.78 102456.78 101456.78 100456.78 100356.78 100256.78 100156.78 100056.78 123456.78 103456.78 Août 2002 Entrées-sorties 012 :: 013 :: 014 :: 015 :: 016 :: 017 :: 018 :: 019 :: 020 :: # enrg ====== 021 :: 022 :: 023 :: 024 :: 025 :: 026 :: 123467 123468 123469 123470 123471 123472 123473 123474 123475 ID ====== 123476 123477 123478 123479 123480 123481 Dieudonné Dieudonné Dieudonné Dieudonné Dieudonné Dieudonné Dieudonné Dieudonné Dieudonné de de de de de de de de de la Trémoïlle2 71 la Trémoïlle3 61 la Trémoïlle4 51 la Trémoïlle5 41 la Trémoïlle6 31 la Trémoïlle7 21 la Trémoïlle8 1 la Trémoïlle 93 la Trémoïlle1 83 NOM AGE ========================== == Dieudonné de la Trémoïlle2 73 Dieudonné de la Trémoïlle3 63 Dieudonné de la Trémoïlle4 53 Dieudonné de la Trémoïlle5 43 Dieudonné de la Trémoïlle6 33 Dieudonné de la Trémoïlle7 23 ecrireToutFichierBinaire: DEBUT ecrireToutFichierBinaire: FIN Le premier ID: 123456 Le dernier ID: 123481 015 :: 123470 Dieudonné de la Trémoïlle5 015 :: 123470 Dieudonné de la Trémoïlle5 004 :: 123459 Dieudonné de la Trémoïlle3 015 :: 123470 Dieudonné de la Trémoïlle5 Fichier Accès # enrg ID ====== ====== 030 :: 123485 029 :: 123484 028 :: 123483 027 :: 123482 026 :: 123481 025 :: 123480 024 :: 123479 023 :: 123478 022 :: 123477 021 :: 123476 020 :: 123475 019 :: 123474 018 :: 123473 017 :: 123472 016 :: 123471 015 :: 123470 014 :: 123469 013 :: 123468 012 :: 123467 011 :: 123466 # enrg ID ====== ====== 010 :: 123465 009 :: 123464 008 :: 123463 007 :: 123462 006 :: 123461 41 51 62 51 102456.78 101456.78 100456.78 100356.78 100256.78 100156.78 100056.78 123456.78 103456.78 SALAIRE ========= 102456.78 101456.78 100456.78 100356.78 100256.78 100156.78 100356.78 600356.78 101456.78 600356.78 direct affiché du dernier au premier enregistrement NOM AGE SALAIRE ========================== == ========= 0 0.00 0 0.00 0 0.00 0 0.00 Dieudonné de la Trémoïlle7 23 100156.78 Dieudonné de la Trémoïlle6 33 100256.78 Dieudonné de la Trémoïlle5 43 100356.78 Dieudonné de la Trémoïlle4 53 100456.78 Dieudonné de la Trémoïlle3 63 101456.78 Dieudonné de la Trémoïlle2 73 102456.78 Dieudonné de la Trémoïlle1 83 103456.78 Dieudonné de la Trémoïlle 93 123456.78 Dieudonné de la Trémoïlle8 1 100056.78 Dieudonné de la Trémoïlle7 21 100156.78 Dieudonné de la Trémoïlle6 31 100256.78 Dieudonné de la Trémoïlle5 51 600356.78 Dieudonné de la Trémoïlle4 51 100456.78 Dieudonné de la Trémoïlle3 61 101456.78 Dieudonné de la Trémoïlle2 71 102456.78 Dieudonné de la Trémoïlle1 81 103456.78 NOM AGE SALAIRE ========================== == ========= Dieudonné de la Trémoïlle 91 123456.78 Dieudonné de la Trémoïlle8 2 100056.78 Dieudonné de la Trémoïlle7 22 100156.78 Dieudonné de la Trémoïlle6 32 100256.78 Dieudonné de la Trémoïlle5 42 100356.78 Alain Lachance. - 26 / 27 - Août 2002 Entrées-sorties 005 004 003 002 001 :: :: :: :: :: 123460 123459 123458 123457 123456 Dieudonné Dieudonné Dieudonné Dieudonné Dieudonné Une exception: de de de de de la la la la la Trémoïlle4 Trémoïlle3 Trémoïlle2 Trémoïlle1 Trémoïlle 52 62 72 82 92 100456.78 101456.78 102456.78 103456.78 123456.78 ID illegal. Cette erreur prévue d'avance ici termine cet exemple. FIN Résultats de la section Alain Lachance. L'accès direct à l'information - 27 / 27 - Août 2002