Byte Streams Character Streams Sink Type

publicité
Fichier et
Stream d’Entrée-Sortie
IFT1025, Programmation 2
Jian-Yun Nie
Concepts
• Notion de fichier et de stream
• Opérations: ouverture, lecture/écriture,
fermeture
• Format texte vs. binaire
• Accès séquentiel vs. aléatoire
• Organisation des indexes
Fichier
• Unité de stockage des données, sur
disque dur,
• stockage permanent (vs. en mémoire vive)
• Un fichier contient un ensemble
d’enregistrements
CPU
• Traitement
Mémoire
vive
fichier
tampon
Fichier en Java
• Stream: une suite de données (octets ou
caractères)
Opérations typiques
• Lecture:
Établir un canal de
communication
– Ouvrir un stream
– Lire tant qu’il y a des données
Relâcher les
– Fermer le stream
• Écriture
ressources allouées
– Ouvrir un stream (ou créer un fichier)
– Écrire des données tant qu’il y en a
Écrire ce qu’il est dans le
– Fermer le stream
tampon, et Relâcher les
ressources allouées
Exemple: TP1
public static void main(String[] args) {
public static void traiter_fichier() {
ouvrir_fichier("liste_mots");
String ligne;
traiter_fichier();
try { // catch EOFException
ligne = input.readLine();
while (ligne != null) {
System.out.println(ligne);
ligne = input.readLine();
}
fermer_fichier();
}
public static void ouvrir_fichier(String nom)
{
try {
input = new BufferedReader(
new FileReader(nom));
}
catch (IOException e) {
System.err.println("Impossible
d'ouvrir le fichier d'entree.\n" +
e.toString());
System.exit(1);
}
}
public static void fermer_fichier() {
try {
input.close();
System.exit(0);
}
catch (IOException e) {
System.err.println("Impossible de
fermer les fichiers.\n" + e.toString());
}
}
Un autre exemple
public void readFile() {
FileReader fileReader = null;
try {
fileReader = new FileReader("input.txt");
int c = fileReader.read();
while (c != -1) {
char d = ((char)c);
c = fileReader.read();
}
} catch (FileNotFoundException e) {
System.out.println("File was not found");
} catch (IOException e) {
System.out.println("Error reading from file");
}
if (fileReader != null) {
try { fileReader.close(); }
catch (IOException e) { /* ignore */ }
}
}
Deux unité de base
• Caractère (2 octets=16 bits) ou octet (8
bits)
• Deux hiérarchies de classe similaires
(mais en parallèle)
Hiérarchies
• En haut des hiérarchies pour stream de
caractères: 2 classes abstraites
• Reader
java.lang.Object
java.io.Reader
• Writer
java.lang.Object
java.io.Writer
• Implantent une partie des méthodes pour lire et
écrire des caractère de 16 bits (2 octets)
Hiérarchie de Stream de caractère
• Les sous-classe de Reader
simple
pré-traitement
• Chaque sous-classe ajoute des méthodes
Hiérarchie de Stream de caractère
• Les sous-classe de Writer
Hiérarchies Byte Stream
System.in
Hiérarchie de Byte Stream
System.out
System.err
Différence: caractère vs byte
• Reader:
– int read()
– int read(char cbuf[])
– int read(char cbuf[], int offset, int length)
• InputStream:
– int read()
– int read(byte cbuf[])
– int read(byte cbuf[], int offset, int length)
Utilisation de différentes classes
• En mémoire:
– CharArrayReader, CharArrayWriter
– ByteArrayInputStream, ByteArrayOutputStream
– Lire/écrire dans un tableau de bytes
– StringReader, StringWriter, StringBufferInputStream
– Lire/écrire dans un String
• Pipe:
– PipedReader, PipedWriter
– PipedInputStream, PipedOutputStream
– Relier la sortie d’un Stream comme une entrée d’un autre stream
(pipeline)
Utilisation
• Fichier (*)
– FileReader, FileWriter
– FileInputStream, FileOutputStream
– Lire/écrire dans un fichier
• Sérialisation
– ObjectInputStream, ObjectOutputStream
• Conversion de données (*)
– DataInputStream, DataOutputStream
– Reconnaître les types de données primitifs
• Impression (*)
– PrintWriter, PrintStream
– Méthodes conviviales pour l’impression
Utilisation
• Buffer (Tampon)
– BufferedReader, BufferedWriter
– BufferedInputStream, BufferedOutputStream
– Créer un tampon: accès plus efficace
• Filtering
– FilterReader, FilterWriter
– FilterInputStream, FilterOutputStream
– Accepte un Stream, le filtre et ensuite passer à un autre Stream
• Convertir entre byte et caractère
– InputStreamReader, OutputStreamWriter
– Lire des bytes en caractère, ou écrire des caractère en byte
Exemple
•
•
Utiliser FileReader et FileWriter
Méthodes simples disponible:
– int read(), int read(CharBuffer []), write(int), ..
– Exemple: copier un fichier caractère par caractère (comme un int)
import java.io.*;
public class Copy {
public static void main(String[] args) throws IOException {
File inputFile = new File("farrago.txt");
File outputFile = new File("outagain.txt");
FileReader in = new FileReader(inputFile);
FileWriter out = new FileWriter(outputFile);
int c;
while ((c = in.read()) != -1) out.write(c);
in.close();
out.close();
}
}
– Méthodes limitées
Fin de fichier: -1
Augmenter les possibilités: wrap
• Créer un stream en se basant sur un autre:
FileReader in = new FileReader(new File("farrago.txt"));
• Avantage:
– Obtenir plus de méthodes
• Dans File: les méthodes pour gérer les fichier (delete(),
getPath(), …) mais pas de méthode pour la lecture
• Dans FileReader: les méthodes de base pour la lecture
– Un autre exemple:
DataOutputStream out = new DataOutputStream(
new FileOutputStream("invoice1.txt"));
• FileOutptuStream: écrire des bytes
• DataOutputStream: les méthodes pour les types de données de
base:
write(int), writeBoolean(boolean), writeChar(int), writeDouble(double),
writeFloat(float), …
Pourquoi faire du wrapping?
• Les streams enrichis ont besoin d’un stream plus primitif
dans son constructeur:
– E.g.
DataInputStream(InputStream in)
DataOutputStream(OutputStream out)
– Impossible de créer un stream directement d’un nom de fichier
comme
new DataOutputStream(“sortie”);
– Besoin de faire du wrapping:
• New DataOutputStream(new FileOutputStream("sortie"))
•
Les streams de base s’occupe des E/S de base, et les
streams enrichies ajoutent d’autres possibilités
• Wrapping: réutiliser les méthodes de base, et ajouter
d’autre méthodes
Hiérarchies
Sink stream
Sink Type
Character Streams
Byte Streams
Memory
CharArrayReader,
CharArrayWriter
ByteArrayInputStream,
ByteArrayOutputStream
StringReader,
StringWriter
StringBufferInputStream
Pipe
PipedReader,
PipedWriter
PipedInputStream,
PipedOutputStream
File
FileReader,
FileWriter
FileInputStream,
FileOutputStream
Wrap dans quelle classe ?
• Dépend des méthodes dont on a besoin
• Quelques exemples
– Lire ligne par ligne:
• Besoin de String readLine() (null à la fin du fichier)
• BufferedReader (sous classe de Reader)
• Traitement:
– Lire une ligne (String)
– Traiter cette ligne avec un Parsing (comme TP1)
• Constructeur: BufferedReader(Reader)
• new BufferedReader(new FileReader("fichier")) (FileReader est
sous-classe de Reader – classe abstraite)
– Écrire ligne par ligne
•
•
•
•
BufferedWriter (sous-classe de Writer)
write(String): écrire un String
newLine(): insérer un changement de ligne
Traitement:
– Organiser une ligne comme String
– Écrire la ligne
Wrap dans quelle classe ?
• Besoin: utiliser les méthodes comme dans
System.out
– write(int), …
– print(…), println(…)
• Stream à utiliser: PrintWriter
• Constructeurs
–
–
–
–
PrintWriter(OutputStream out)
PrintWriter(Writer out)
new PrintWriter(new FileOutputStream("fichier"))
new PrintWriter(new FileWriter("fichier"))
Wrap dans quelle classe ?
• Besoin: écrire les données des types
primitifs
– writeChar(Char), writeFloat(float), …
• Stream à utiliser: DataOutputStream
• Constructeur:
– DataOutputStream(OutputStream out)
– new DataOutputStream(new
FileOutputStream("fichier"))
Interface: JFileChooser
JFileChooser chooser = new JFileChooser();
FileReader in = null;
if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION)
{
File selectedFile = chooser.getSelectedFile();
reader = new FileReader(selectedFile);
...
}
Attention au format
• Stocker les données dans un fichier
• Mais aussi pouvoir les ressortir
• E.g. stocker le nom et la date de naissance:
– GéraldTremblay19500101SusanneRoy19800406…
• Au moment de stocker, déterminer un format pour
pouvoir les relire
Façon simple:
Gérald Tremblay 19500101 Susanne Roy 19800406…
– Lire prénom (jusqu’à l’espace), nom, date de naissance (il faut
ensuite la décomposer)
Format
• Stocker en binaire ou en caractère?
– Caractère: lisible (e.g. avec more, vi, etc.)
– Binaire: transformer en code binaire:
•
•
•
•
int: 4 octets
Long: 8 octets
char: 2 octet
…
– Valeur 12345 (entier)
•
•
•
•
En caractère: 12345 (10 octets)
En binaire: 0 0 48 57 (4 octets) = 48*256+57
Binaire plus économique en espace
Binaire = espace fixe (facilite la lecture)
Exemple en binaire
• Pour un compte bancaire:
– No. de compte: entier
– Montant: double
• Pour écrire un enregistrement (pour un compte)
– writeInt(int)
– writeDouble(double)
– Classe: DataOutputStream
• Pour lire un enregistrement
– readInt()
– readDouble()
– Classe: DataInputStream
• Penser aux écritures et aux lectures en même temps
DataInputStream et
DataOutputStream
• Lire et écrire des données des types de base
– readBoolean(), readInt(), readFloat, readChar(), readLine();
readUTF(), …
– writeBoolean(boolean), writeInt(int), writeFloat(float),
writeChar(char), writeChars(String), writeUTF(String), …
– readUTF et writeUTF:
• Entier en binaire correspondant à la longueur du String + String
• Exemple: liste_mots
^@&sur
prep sing sur rel^@(laquelle
pron_rel
fem sing ^@&systeme
n masc sing outil ^@ non
adv non rel^@$echeance
n fem sing temps^@^^entite
…
Références:
http://java.sun.com/j2se/1.5.0/docs/api/java/io/DataOutputStream.html
http://java.sun.com/j2se/1.5.0/docs/api/java/io/DataInputStream.html
Sérialiser
• Convertir un objet (avec une structure) en une
suite de données dans un fichier
• Reconvertir du fichier en un objet
• Utilisation: avec ObjectOutputStream
Employee[] staff = new Employee[3];
ObjectOutputStream out = new
ObjectOutputStream(new
FileOutputStream("test2.dat"));
out.writeObject(staff);
out.close();
Sérialiser
• Utilité de sérialisation
– Stocker un objet dans un fichier
– Créer une copie d’objet en mémoire
– Transmettre un objet à distance
• Devient une transmission de String
Sérialiser
• Pour pouvoir sérialiser un objet:
– sa classe doit implanter l’interface Serializable
– Interface Serializable:
• Pas de méthode exigée
• Mais on peut réimplanter readObject() et writeObject() si on ne se
contente pas de la version par défaut: defaultReadObject(),
defaultWriteObject()
• private void writeObject(java.io.ObjectOutputStream out)
throws IOException
• private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException
– Beaucoup de classes existantes sont sérialisables (e.g.
ArrayList)
Sérialiser: une classe sérialisable
try{
import java.io.*;
import java.util.*;
import java.util.logging.*;
public class ExerciseSerializable {
public static void main(String[] aArguments) {
//create a Serializable List
List quarks = new ArrayList();
quarks.add("up");
quarks.add("down");
quarks.add("strange");
quarks.add("charm");
quarks.add("top");
quarks.add("bottom");
ArrayList est sérialisable
OutputStream file = new FileOutputStream( "quarks.ser" );
OutputStream buffer = new BufferedOutputStream( file );
output = new ObjectOutputStream( buffer );
output.writeObject(quarks);
}
catch(IOException ex){
fLogger.log(Level.SEVERE, "Cannot perform output.", ex);
}
finally{
try {
if (output != null) {
//flush and close "output" and its underlying streams
output.close();
}
}
catch (IOException ex ){
fLogger.log(Level.SEVERE, "Cannot close output
stream.", ex);
}
}
Définir une clase sérialisable
class Employee implements Serializable
{ public Employee(String n, double s, Date d)
{ name = n;
salary = s;
hireDate = d;
}
class Manager extends Employee
{ public Manager(String n, double s, Date d,
Employee e)
{ super(n, s, d);
secretary = e;
}
public Employee() {}
public Manager() {}
public void raiseSalary(double byPercent)
{ salary *= 1 + byPercent / 100;
}
public void raiseSalary(double byPercent)
{ // add 1/2% bonus for every year of service
Date today = new Date();
double bonus = 0.5 * (today.getYear() hireYear());
super.raiseSalary(byPercent + bonus);
}
public int hireYear()
{ return hireDate.getYear();
}
public void print()
{ super.print();
System.out.print("Secretary: ");
if (secretary != null) secretary.print();
}
public void print()
{ System.out.println(name + " " + salary
+ " " + hireYear());
}
private double salary;
private String name;
private Date hireDate;
}
private Employee secretary;
}
Utiliser une classe sérialisable
ObjectOutputStream out = new
ObjectOutputStream(new
FileOutputStream("test2.dat"));
out.writeObject(staff);
out.close();
import java.io.*;
import java.util.*;
class ObjectRefTest
{ public static void main(String[] args)
{ try
{
Employee[] staff = new Employee[3];
ObjectInputStream in = new
ObjectInputStream(
new FileInputStream ("test2.dat"));
Employee[] newStaff =
(Employee[])in.readObject();
Employee harry = new Employee
("Harry Hacker", 35000,
new Date(1989,10,1));
staff[0] = harry;
staff[1] = new Manager("Carl Cracker",
75000,
new Date(1987,12,15), harry);
staff[2] = new Manager("Tony Tester",
38000,
new Date(1990,3,15), harry);
for (int i = 0; i < newStaff.length; i++)
newStaff[i].raiseSalary(100);
for (int i = 0; i < newStaff.length; i++)
newStaff[i].print();
}
catch(Exception e)
{ e.printStackTrace();
System.exit(1);
}
}
}
Sortie de sérialisation
vi test2.dat:
¬í^@^Eur^@^K[LEmployee;ü¿6^QÅ<91>^QÇ^B^@^@xp^@^@^@^Csr^@
^HEmployee~BÅ<89>^V<99>q=^B^@^CD^@^FsalaryL^@^HhireDatet^@^
PLjava/util/Date;L^@^Dnamet^@^RLjava/lang/String;xp@á^W^@^@^@^@
^@sr^@^Njava.util.Datehj<81>^AKYt^Y^C^@^@xpw^H^@^@7^Y×<8c>4<
80>xt^@^LHarry Hackersr^@^GManager^U<9d><93>þ<8f>Íq^[^B^@^AL
^@secretaryt^@LEmployee;xq^@~^@^B@òO<80>^@^@^@^@sq^@~^@
^Fw^H^@^@7^L¥@t<80>xt^@^LCarl Crackerq^@~^@^Esq^@~^@@â
<8e>^@^@^@^@^@sq^@~^@^Fw^H^@^@7^])^N<92>^@xt^@^KTony
Testerq^@~^@ ^E
Lisible par désérialisation (readObject()):
Harry Hacker 70000.0 1989
Carl Cracker -555750.0 1988
Secretary: Harry Hacker 70000.0 1989
Tony Tester -281960.0 1990
Secretary: Harry Hacker 70000.0 1989
Accès séquentiel vs. aléatoire
• Séquentiel: Première donnée, suivante, …
– Approprié pour traiter toutes les données
• Aléatoire (random): positionner à un endroit, lire les
données à partir de cette position
– Approprié pour sélectionner certaines données à traiter
– Question importante:
• Comment déterminer la position correcte ?
RandomAccessFile
•
•
•
•
Un Stream en format binaire
Écrire et lire (selon l’ouverture)
Possibilité de positionner avec seek(long)
Exemple:
file = new RandomAccessFile(filename, "rw");
file.seek(100);
int accountNumber = file.readInt();
double balance = file.readDouble();
Référence:
http://java.sun.com/j2se/1.4.2/docs/api/java/io/RandomAccessFile.html
RandomAccessFile
• Ouverture:
file = new RandomAccessFile(filename, "rw");
Modes:
r: lecture seulement
rw: lecture et ecriture
Mode
RandomAccessFile
• Lecture ou écriture
– Sans seek:
• Position au début du fichier
• Écrire et lire à partir de cette position comme un
accès séquentiel
– seek(long)
• Positionner à la position (no. de bytes à partir du
début)
• Lire ou écrire à partir de cette position
Position
• Comment déterminer la bonne position?
– Solution simple:
• Chaque enregistrement = taille fixe
• Pour un enregistrement: déterminer son numéro
• seek(taille*no)
– Solution plus complexe:
• Organiser un indexe
• Utiliser une table de hashage
Taille fixe pour un enregistrement
008: public class BankData
009: {
024: public void open(String filename)
025:
throws IOException
026: {
027:
if (file != null) file.close();
028:
file = new RandomAccessFile(filename, "rw");
029: }
035: public int size()
036:
throws IOException
037: {
038:
return (int) (file.length() / RECORD_SIZE);
039: }
071:
072:
073:
074:
075:
076:
077:
078:
079:
080:
081:
082:
public int find(int accountNumber)
throws IOException
{
for (int i = 0; i < size(); i++)
{
file.seek(i * RECORD_SIZE);
int a = file.readInt();
if (a == accountNumber) // Found a match
return i;
}
return -1; // No match in the entire file
}
056:
057:
058:
059:
060:
061:
062:
063:
public BankAccount read(int n)
throws IOException
{
file.seek(n * RECORD_SIZE);
int accountNumber = file.readInt();
double balance = file.readDouble();
return new BankAccount(accountNumber, balance);
}
089:
090:
091:
092:
093:
094:
095:
096:
097:
098:
099:
100:
101:
102:
103: }
public void write(int n, BankAccount account)
throws IOException
{
file.seek(n * RECORD_SIZE);
file.writeInt(account.getAccountNumber());
file.writeDouble(account.getBalance());
}
private RandomAccessFile file;
public static final int INT_SIZE = 4;
public static final int DOUBLE_SIZE = 8;
public static final int RECORD_SIZE
= INT_SIZE + DOUBLE_SIZE;
Accès dans RandomAccessFile
• Exploiter seek pour déterminer la position pour
lire ou écrire un enregistrement
– Séquentiel: lire chaque enregistrement à partir du
début
– Direct: déterminer une position selon une clé, et
lire/écrire l’enregistrement
• Position = no. de compte * taille:
– clé = no. de compte
• Position est déterminer selon une conversion avec une clé
(e.g. code permanent GILB76022304)
– Non numérique
– Non compact: valeur non continue
Cas 1: Recherche binaire
• Les enregistrements sont stockés dans l’ordre
croissant des clés
• Accès binaire (O(log(n)):
– Chercher(clé, début, fin):
• Lire le milieu
• Si clé_milieu == clé, trouvé
• Si clé_milieu < clé,
– Si (milieu – début) < 2, introuvable;
– Sinon cherche(clé, début, milieur-1)
• Si clé_milieu > clé,
– Si (fin – milieu) < 2, introuvable;
– Sinon cherche(clé, milieu+1, fin)
Recherche binaire
Fichier
1
3
6
8
10
16
20
Cas 2: table de hashage
• Table de hashage: déterminer une position
pour chaque clé
• Fonction de hashage: clé entier
• Contraintes:
– Le plus compacte possible (pas beaucoup de
positions vides)
– Le moins de conflit possible (2 clés – même
position)
Exemple simple
• Transformer un code permanent en un entier:
– VALB23027502
code
– 4 premiers caractères: A-Z:
(1-26)4
– 2 chiffres: 01-31:
1-31
– 2 chiffres: 01-12, 5-62:
1-24
– 2 chiffres: 50-40 (1950-2040):
0-90
– 2 chiffres: 01-99:
1-99
• Fonction1(clé) = concatener les codes
– Compacte ?
00*** et 27***, **00*** et **32*** non utilisés
• Fonction2(clé) = code(L1)*…*code(L4)*code(jour)*
code(mois)*code(année)*code(derniers_ch)
– Conflit? VALB23027502 = VALB23017504
Approche générale
• Valeur non conflictuelle (mais relativement
compact)
• Déterminer la taille approximative du fichier
souhaitée (taille)
• Valeur % primaire
– Primaire est un nombre proche de la taille
– E.g. <10 000 enregistrements: 10007, 10009, 10037,
…12007, …
– Prévoir plus large
– Prévoir un mécanisme pour résoudre le conflit
• Aller à la suivante
• Rehashage: appliquer une autre fonction de hashage
• …
Cas 3: indexe
• Maintenir une structure d’indexe (clé= lettre +
nombre) 0
Clé=
a21
a
b
c
…
…
20
21
pos
0
10
20
…
90
z
90
pos
Téléchargement