500 likes | 677 Views
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.
E N D
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 • Traitement CPU fichier Mémoire vive tampon
Fichier en Java • Stream: une suite de données (octets ou caractères)
Opérations typiques • Lecture: • Ouvrir un stream • Lire tant qu’il y a des données • Fermer le stream • Écriture • Ouvrir un stream (ou créer un fichier) • Écrire des données tant qu’il y en a • Fermer le stream Établir un canal de communication Relâcher les ressources allouées Écrire ce qu’il est dans le tampon, et Relâcher les ressources allouées
public static void main(String[] args) { ouvrir_fichier("liste_mots"); traiter_fichier(); 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 traiter_fichier() { String ligne; try { // catch EOFException ligne = input.readLine(); while (ligne != null) { System.out.println(ligne); ligne = input.readLine(); } } 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()); } } Exemple: TP1
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
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)
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"); try{ 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); } } Sérialiser: une classe sérialisable ArrayList est sérialisable
class Employee implements Serializable { public Employee(String n, double s, Date d) { name = n; salary = s; hireDate = d; } public Employee() {} public void raiseSalary(double byPercent) { salary *= 1 + byPercent / 100; } public int hireYear() { return hireDate.getYear(); } public void print() { System.out.println(name + " " + salary + " " + hireYear()); } private double salary; private String name; private Date hireDate; } class Manager extends Employee { public Manager(String n, double s, Date d, Employee e) { super(n, s, d); secretary = e; } public Manager() {} 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 void print() { super.print(); System.out.print("Secretary: "); if (secretary != null) secretary.print(); } private Employee secretary; } Définir une clase sérialisable
import java.io.*; import java.util.*; class ObjectRefTest { public static void main(String[] args) { try { Employee[] staff = new Employee[3]; 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); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("test2.dat")); out.writeObject(staff); out.close(); ObjectInputStream in = new ObjectInputStream( new FileInputStream ("test2.dat")); Employee[] newStaff = (Employee[])in.readObject(); 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); } } } Utiliser une classe sérialisable
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
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: public int find(int accountNumber) 072: throws IOException 073: { 074: for (int i = 0; i < size(); i++) 075: { 076: file.seek(i * RECORD_SIZE); 077: int a = file.readInt(); 078: if (a == accountNumber) // Found a match 079: return i; 080: } 081: return -1; // No match in the entire file 082: } 056: public BankAccount read(int n) 057: throws IOException 058: { 059: file.seek(n * RECORD_SIZE); 060: int accountNumber = file.readInt(); 061: double balance = file.readDouble(); 062: return new BankAccount(accountNumber, balance); 063: } 089: public void write(int n, BankAccount account) 090: throws IOException 091: { 092: file.seek(n * RECORD_SIZE); 093: file.writeInt(account.getAccountNumber()); 094: file.writeDouble(account.getBalance()); 095: } 096: 097: private RandomAccessFile file; 098: 099: public static final int INT_SIZE = 4; 100: public static final int DOUBLE_SIZE = 8; 101: public static final int RECORD_SIZE 102: = INT_SIZE + DOUBLE_SIZE; 103: } Taille fixe pour un enregistrement
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
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) Clé=a21