Manipulation des fichiers séquentiels avec TFileStream

Tony Baheux

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.

- Titre : Manipulation des fichiers séquentiels avec TFileStream

- Auteur : Tony Baheux

- Parution : 1 janvier 2005

- Licence : 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.

Sommaire

- I. Lecture

- II. Écriture

- III. Conclusion

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.

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

Étudiez 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 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.

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

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

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.

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