Manipulation des fichiers séquentiels avec TFileStream

Ce tutoriel 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.

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. 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 dont la taille n'est pas fixe (comme : String et les tableaux dynamiques). 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.

 
Sélectionnez
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 :

 
Sélectionnez
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 à

 
Sélectionnez
F.Position := 0;

Mettra les choses en ordre.

La lecture se fait par l'intermédiaire de la syntaxe suivante :

 
Sélectionnez
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].

Étudiez l'exemple, je pense qu'il est assez parlant.

 
Sélectionnez
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 et affecte les données à un record

    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);
          try
                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 with

                        end; // end while

                        Result := true;
                end
                else
                begin
                        MessageDlg('Erreur ce n''est pas un fichier test.', mtError, [mbOk], 0);
                        Result := false;
                end;
          finally
                // Libération du fichier
                F.free;
          end; // try.. finally

    except
          // Traitement des exceptions
          Result := False;
      on EInOutError do
      begin        
        MessageDlg('Erreur d''E-S fichier.', mtError, [mbOk], 0);
      end;
      on EReadError do
      begin
         MessageDlg('Erreur de lecture sur le fichier.', mtError, [mbOk], 0);
      end;
      else
      begin
         MessageDlg('Erreur sur le fichier.', mtError, [mbOk], 0);
      end;
    end; // try..except

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 tutoriel, à ce propos vous pouvez télécharger l'ébauche du programme pour l'étudier et le compléter (voir à la 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.

II. Écriture

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.

 
Sélectionnez
F := TFileStream.Create(Filetmp, fmCreate or fmShareExclusive);

Ouverture en écriture et en mode non partagé (lecture et écriture impossible par d'autre application)

 
Sélectionnez
F.WriteBuffer(MaVariable, SizeOf(TMaVariable));

Écriture de MaVariable en indiquant sa taille, comme pour la lecture en transmet en générale 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.

 
Sélectionnez
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);
          try
                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;
      finally           
            // Libèration du Fichier

                F.Free;
      end; // try..finally
          // S'il y a eu une erreur, on est sûr d'avoir libèrer le fichier
          // mais le code ci-dessous ne sera pas exécuter, on passe directement à la partie except.
          
      // Détruit Nom et renome temp.zio en Nom

      DeleteFile(Nom);
      if RenameFile(Filetmp, ExtractFileName(Nom)) then
          Result := true
      else
          Result := false;
  except
      Result := False;
      on EInOutError do
      begin
        MessageDlg('Erreur d''E-S fichier : Fichier non enregistré.', mtError, [mbOk], 0);
      end;
      on EWriteError do
      begin
         MessageDlg('Erreur d''ecriture dans le fichier. Fichier non enregistré', mtError, [mbOk], 0);
      end;
      else
      begin
         MessageDlg('Erreur sur le fichier. Fichier non enregistré.', mtError, [mbOk], 0);
      end;
  end; // try..except

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.

III. 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.

Sommaire

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2005 Tony Baheux. Aucune reproduction, même partielle, ne peut être faite de ce site et 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.