Fichiers binaires, objets

publicité
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
Téléchargement