IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Guide Pascal et Delphi

Guide Pascal et Delphi

Date de publication : 10/04/2000 , Date de mise à jour : 01/04/2008


XX. TFileStream
XX-A. Introduction
XX-B. Lecture
XX-C. Ecriture
XX-D. Conclusion


XX. TFileStream


XX-A. Introduction

Ce tutorial a pour but de vous présenter sommairement la classe TFileStream et son utilisation dans la lecture et l'écriture de fichier à travers des exemples. Avant de commencer, il est fortement conseillé de (re)lire les fichiers séquentiels dans le guide Delphi de Frédéric Beaulieu. En effet, la classe TFileStream permet de simplifier la manipulation de fichier séquentiel.


XX-B. Lecture

L'exemple ci-dessous manipule un record contenant des champs de type différent pour montrer comment procéder. En fait, le seul type qui pose problème est le type (String, tableaux dynamiques) dont la taille n'est pas fixe. Le type énumération bien que ne posant pas de problème passe quand même par une subtilité, il est impossible d'écrire directement une variable de type énuméré. C'est pourquoi nous manipulons un entier issu de la conversion du type énuméré.

La première chose à faire, est d'ouvrir un flux sur le fichier. On utilise la même commande pour la lecture ou l'écriture en spécifiant les paramètres adéquats. La commande ci dessous, ouvre le fichier Nom en lecture et en mode partagé pour la lecture.
F := TFileStream.Create(Nom, fmOpenRead or fmShareDenyWrite);
En mode partagé en lecture seule, l'écriture par d'autre application est impossible tant que le flux est ouvert d'où l'obligation de fermer le flux une fois le travail fini. Par contre, il est accessible en lecture. Ceci est fait en libérant la ressource :
F.Free;
Remarquez que je n'ai pas utilisé de Try..Finally et que c'est un tort (mais vous aurez corrigé de vous-même). Voir la chapitre sur la gestion des exceptions si vous ne savez pas comment faire.
Pour commencer la lecture depuis le début (ce qui est préférable), un simple appel à
F.Position := 0;
Mettra les choses en ordre.
La lecture se fait par l'intermédiaire de la syntaxe suivante :
F.ReadBuffer(Mavariable, SizeOf(TMavariable));
Ou TMavariable indique la taille de MaVariable. En général, on transmet le type de la variable, ex integer s'il s'agit d'un entier. Mais pour certains types comme les strings il faut indiquer la taille de la donnée. Dans le cas des strings, il faut transmettre un pointeur, on utilise alors MaVariable[1].
Etudiez l'exemple, je pense qu'il est assez parlant.

type
    TEnumTest = (etPos1, etPos2, etPos3, etPos4);

    TMonRecord = record
       entier : integer;
       reel : double;
       enum : TEnumTest;
       chainelimit : string[100];
       chaine : string;
    end;
 
 
function OuvrirFichier(Nom : TFileName;var mrec : TMonRecord) : boolean;
var
   F : TFileStream;
   i : integer;
begin
    // Ouvre un fichier test
    F := nil;
    try
      // Ouverture d'un flux sur le fichier en lecture. Le fichier reste accessible en lecture seule pour d'autres appli.
      F := TFileStream.Create(Nom, fmOpenRead or fmShareDenyWrite);
      F.Position := 0; // Début du flux
      if (pos('.zio',ExtractFileName(Nom))>0) then // Vérification du type du Fichier
      begin
         // Lecture du fichier
         while (F.position < F.Size) do // Tant que la fin du fichier n'est pas atteinte faire :
         begin
            with mrec do // voir TMonRecord pour savoir quelle type de donnée nous avons.
            begin
               // Lit la valeur et déplace la position en cours
               // La première valeur lue est un entier (le fichier a été enregistré ainsi)
               F.ReadBuffer(entier, SizeOf(integer));
               // Ensuite nous avons un réel
               F.ReadBuffer(reel, SizeOf(Double));
               // De nouveau un entier mais sa valeur n'est pas directement exploitable dans mrec. 
               // On le convertit, ici il s'agit d'un type énuméré. 
               F.ReadBuffer(i, SizeOf(integer));
               enum := TEnumTest(i);
               // On lit ensuite une Chaine de caractère dont la taille est limitée (string[taille]).
               F.ReadBuffer(chainelimit, SizeOf(Chainelimit));
               // On lit un entier correspondant à la taille de la chaine qui suit
               F.ReadBuffer(i, SizeOf(i));
               // Allocation d'assez d'espace dans la chaine pour la lecture
               SetLength(chaine, i);
               // Lecture de la chaine, on transmet un pointeur sur la chaine soit Chaine[1].
               F.ReadBuffer(chaine[1], i);
            end;
         end;
         Result := true;
      end
      else
      begin
         MessageDlg('Erreur ce n''est pas un fichier test.', mtError, [mbOk], 0);
         Result := false;
      end;
      F.Free;
    except
      on EInOutError do
      begin
        Result := False;
        F.Free;
        MessageDlg('Erreur d''E-S fichier.', mtError, [mbOk], 0);
      end;
      on EReadError do
      begin
         Result := False;
         F.Free;
         MessageDlg('Erreur de lecture sur le fichier.', mtError, [mbOk], 0);
      end;
      else
      begin
         Result := False;
         F.Free;
         MessageDlg('Erreur sur le fichier.', mtError, [mbOk], 0);
      end;
    end; // try
end;
Cet exemple lit le fichier et récupère tous les enregistrements contenue dans le fichier mais ne garde que le dernier car une seule variable est utilisé pour stocker le résultat. Je sais, c'est nul mais bon j'ai la flemme de mettre un tableau dynamique (ou alors le nombre d'enregistrement contenus dans le fichier doit être fixe) stockant des enregistrements et d'ajouter chaque enregistrement à ce tableau en incrémentant l'index en cours. Ce qui d'ailleurs vous ferez un bon tutorial, à ce propos vous pouvez télécharger l'ébauche du programme pour l'étudier et le compléter (voir à la XX-D Conclusion).

Un point important maintenant, même si cet exemple ne manipule qu'un enregistrement, rien ne vous interdit de stocker d'autres valeurs dans le type de fichier. Vous pourriez par exemple enregistrer le nombre d'enregistrement qu'il contient, un commentaire ou l'âge du capitaine. L'essentiel est de savoir dans quel ordre les données sont agencées et leur taille. Passons donc à l'écriture.


XX-C. Ecriture

Comme vous l'avez vu plus haut, le point délicat mis à part la manipulation de certains type de donné est l'agencement des données. Vous ne devez pas perdre de vue l'ordre dans lequel vous placez vos données dans le fichier, sinon il sera illisible. Le modus operandi de l'écriture est strictement le même que la lecture (si ça, c'est pas une bonne nouvelle). La seule différence est dans les paramètres d'ouverture du fichier et l'utilisation de la commande d'écriture à la place de lecture.
F := TFileStream.Create(Filetmp, fmCreate or fmShareExclusive);
Ouverture en écriture et en mode non partagé (lecture et écriture impossible par d'autre application)
.WriteBuffer(MaVariable, SizeOf(TMaVariable));
Ecriture de MaVariable en indiquant sa taille, comme pour la lecture en transmet en général le type de la variable. N'oubliez pas de libérer la ressource une fois le travail terminé et avant d'essayer de manipuler le fichier (et oui, vous l'avez verrouillé).
Voir l'exemple ci-dessous.

function EnregistreFichier(Nom : TFileName;mrec : TMonRecord):boolean;
var
   F: TFileStream;
   Filetmp : TFileName;
   Chem_tmp, Nom_tmp : string;
   i : integer;
begin
   // Enregistrement d'un fichier
   Chem_tmp := ExtractFilePath(Nom);
   Nom_tmp := '~temp.zio';
   Filetmp := TFileName(Chem_tmp+'\'+Nom_tmp);
   F := nil;
   try
      DeleteFile(Filetmp);
      // Ouverture d'un flux sur le fichier, en création et de façon exclusive
      F := TFileStream.Create(Filetmp, fmCreate or fmShareExclusive);
      F.Position := 0; // Début du flux
      // Ecriture du fichier
      with mrec do
      begin
        // On écrit le champ entier en premier
         F.WriteBuffer(entier, SizeOf(integer));
        // Puis le champ Réel
         F.WriteBuffer(reel, SizeOf( Double));
        // On convertit le champ de type énuméré en entier, on ne peut pas écrire directement une variable de type enuméré.
         i := Ord(enum);
        // On écrit l'entier correspondant au champ énuméré
         F.WriteBuffer(i, SizeOf(integer));
        // On écrit la chaîne à taille fixe, aucune difficulté.
         F.WriteBuffer(chainelimit, SizeOf(chainelimit));
        // Pour la chaîne de type string, il nous faut indiquer la taille de la chaîne sinon nous ne pourrions plus la relire
         i := Length(chaine);
        // On écrit donc la taille de la chaîne
         F.WriteBuffer(i, SizeOf(i));
        // Puis la chaîne en indiquant sa taille et en transmettant un pointeur.
         F.WriteBuffer(chaine[1], i);
      end;
      F.Free;
      // detruit Nom et renome temp.zio en Nom
      DeleteFile(Nom);
      if RenameFile(Filetmp, ExtractFileName(Nom)) then
          Result := true
      else
          Result := false;
  except
      on EInOutError do
      begin
        Result := False;
        F.Free;
        MessageDlg('Erreur d''E-S fichier : Fichier non enregistré.', mtError, [mbOk], 0);
      end;
      on EWriteError do
      begin
         Result := False;
         F.Free;
         MessageDlg('Erreur d''ecriture dans le fichier. Fichier non enregistré', mtError, [mbOk], 0);
      end;
      else
      begin
         Result := False;
         F.Free;
         MessageDlg('Erreur sur le fichier. Fichier non enregistré.', mtError, [mbOk], 0);
      end;
  end; // try
end;
Comme pour la lecture, je me suis limité à l'écriture d'un seul enregistrement. Si vous avez réalisé une fonction de lecture lisant (essayant de lire, le fichier peut très bien ne contenir qu'un seul enregistrement) plusieurs enregistrements dans le fichier, votre fonction d'écriture devrait permettent d'écrire plusieurs enregistrements également. En utilisant un tableau d'enregistrement, il suffit de le parcourir et d'écrire chaque enregistrement, les uns à la suite des autres. Vous pouvez également indiquer d'autres informations dans le fichier.


XX-D. Conclusion

Deux points sont capitaux dans les fichiers séquentiels. Le premier si une variable est de taille dynamique, alors la taille devra faire partie des informations enregistrées dans le fichier. Le second, les fonctions de lecture et d'écriture doivent être symétriques, les données attendues par ses fonctions doivent l'être dans le même ordre. Si vous écrivez le nom d'une personne, lors de la lecture vous lirez le nom de cette personne au même moment.

Pour ceux qui voudront compléter le programme pour qu'il puisse lire et écrire plusieurs enregistrements ou pour voir le code 'complet', vous pouvez le télécharger en cliquant ici.

 

Copyright © 2008 Frédéric Beaulieu. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.