Accueil
Rechercher:
sur developpez.com sur les forums
Forums | Tutoriels | F.A.Q's | Participez | Hébergement | Contacts
Club Emploi Blogs   TV   Dév. Web PHP XML Python Autres 2D-3D-Jeux Sécurité Windows Linux PC Mac
Accueil Conception Java DotNET Visual Basic  C  C++ Delphi MS-Office SQL & SGBD Oracle  4D  Business Intelligence
FORUMS DELPHI F.A.Q DELPHI TUTORIELS DELPHI LIVRES COMPOSANTS SOURCES DEFI TELECHARGEZ DELPHI TV

Sauvegarder des chaînes longues dans un espace réduit

Date de publication : 26/04/2002

Date de mise a jour : 16/08/2004

Par Jean-Luc Mellet(Alphomega) (http://alphomega.developpez.com/)
 

Gestion de la sauvegarde des chaines longues dans des fichiers typés.


Avant-propos
1. Les solutions au problème
1.1. La mauvaise
1.2. La bonne (à mon avis)
2. Créer son propre composant de sauvegarde


Avant-propos


Ces derniers temps, une question est souvent revenue sur le forum Delphi à propos de la sauvegarde dans un fichier de record. Quand on utilise un "membre" de type string dans un record et que l'on veut s'en servir pour créer un fichier de record à des fins de sauvegarde, Delphi génère une erreur indiquant:

[Erreur] Unit1.pas(48): Le type 'TMonRec' nécessite une finalisation - non autorisé dans type fichier
Si on appuis sur F1 après sélection de cette erreur, Delphi nous explique:

Extrait de l'aide Delphi de Borland

Certains types sont traités de manière spéciale par le compilateur sur une base interne, c'est-à-dire qu'ils doivent être correctement terminés pour libérer toutes les ressources qu'ils peuvent détenir actuellement. Comme le compilateur ne peut pas déterminer quel type est actuellement stocké dans une section variant d'enregistrement au moment de l'exécution, il est impossible de garantir que ces types de données spéciaux sont correctement terminés.

program Produce; type Data = record name : string; end; var inFile : file of Data; begin end. {String est un des types de données nécessitant une finalisation, et comme tel,} {ils ne peuvent pas être stockés dans un type File.} program Solve; type Data = record name : array [1..25] of Char; end; var inFile : file of Data; begin end.
Une solution simple, pour le cas de String, est de re-déclarer le type comme tableau de caractères. Pour les autres cas nécessitant une finalisation, il devient de plus en plus difficile de maintenir une structure de fichier binaire avec des fonctions Pascal standard, telles que 'file of'. Dans ces situations, il est probablement plus simple d'écrire des routines d'E/S de fichier spécialisées.

Fin de l'extrait de l'aide Delphi de Borland

Alors, quelles sont les solutions ?


1. Les solutions au problème


Comme il est dit dans l'aide, on a plusieurs possibilités. J'en ai détecté 1 bonne et 1 moins bonne.


1.1. La mauvaise


Sans être vraiment mauvaise, la première a un inconvénient majeur. Elle utilise parfois inutilement de l'espace disque. Nous allons voir pourquoi. Cette solution consiste à déclarer le membre de la structure avec une longueur fixe. On a alors le choix de créer un ShortString (String[xx]), ou bien un tableau de char. Par exemple:

TMonRec = record Nom: array[0..49] of char; Age: integer; end; // ou bien TMonRec = record Nom: string[50]; Age: integer; end;
Dans ce cas, plus de problème ! Delphi est d'accord.
L'ennui, c'est que tout le monde n'a pas un nom de 50 caractères. Imaginons une personne qui s'appelle M. Po. Pour stocker son nom, nous utiliserons 50 octets, donc 48 de trop. Quel gaspillage !!!

Dans le cas d'un nom, la structure string[50] est valable car un nom fait rarement plus de 50 caractères. Mais si l'on voulait enregistrer dans notre record des chaînes de caractères plus longues, nous serions limité à 255 caractères, car une chaîne de longueur définie est un ShortString, et donc avec cette limitation. Alors comment éviter ce gaspillage de place tout en sauvegardant ce qu'on veut dans un fichier ?


1.2. La bonne (à mon avis)


La bonne solution quand on veut sauvegarder des chaînes de longueurs différentes, passe par l'oubli des "file of " pour créer son propre système de sauvegarde. Nous allons voir comment sauvegarder une chaîne de caractères avec juste ce qu'il faut d'espace disque.

Si l'on reprend notre structure exemple,

TMonRec = record Nom: string; Age: integer; end;
nous savons que Delphi ne nous permettra pas de créer un file of TMonRec. Il va donc falloir créer notre propre procédure d'I/O dans le fichier de sauvegarde.

// Fic est un TFileStream créé plus haut dans le code procedure TForm1.SauveRec(Rec: TMonRec); begin // Sauvegarde du membre Nom : voir plus bas Fic.Write(Rec.Age, sizeof(Rec.Age)); end; procedure TForm1.LitRec(var Rec: TMonRec); begin //Lecture du membre Nom : voir plus bas Fic.Read(Rec.Age, sizeof(Rec.Age)); end;
Pour le problème de Rec.Nom, le premier réflexe du débutant sera de coder comme ceci:

Fic.Write(Rec.Nom, sizeof(Rec.Nom);
Hélas, le résultat sera désastreux. Pourquoi ? Tout simplement parce pour Delphi, une variable string ne représente qu'une adresse mémoire. Donc, cette façon de procéder va sauvegarder n'importe quoi dans une emplacement de 4 octets (la taille du pointer).

De la même façon:

Fic.Write(Rec.Nom, Length(Rec.Nom);
ne fonctionnera pas. Pourquoi ? Regardons les définitions de TFileStream.Write et TFileStream.Read:

function Write(const Buffer; Count: Longint): Longint; override; function Read(var Buffer; Count: Longint): Longint; override;
Pour Write, le paramètre Buffer est un const. Nous pourrions donc lui assigner notre Rec.Nom puisque c'est un pointer, donc const lui aussi. Le problème, c'est que dans ce cas, nous allons sauver la valeur du pointer et pas la chaîne. Pour Read, au contraire, Buffer est un var. Mais notre "pointer" Rec.Nom est un pointer, donc const. Ca ne va pas non plus.

La solution est simple: Au lieu d'utiliser le pointer, nous utiliserons le "vrai" premier caractère de la chaîne.

// Fic est un TFileStream créé plus haut dans le code procedure TForm1.SauveRec(Rec: TMonRec); begin Fic.Write(Rec.Nom[1], length(Rec.Nom); //... end;
Là, tout va bien ! Nous disons à Delphi: "Ecris dans le fichier ce que tu trouves à partir Rec[1] (premier caractère "réel") sur une longueur de length(Rec.Nom), donc, tous les caractères, et arrètes toi là. Ainsi, quelle que soit la longueur de la chaîne, on n'utilise QUE la place nécessaire.

Le problème va se poser à la lecture du fichier. Comme Read attend une longueur de caractère à lire, il faut bien lui fournir cette longueur. La solution est toute simple. Il suffit de sauvegarder aussi la longueur de la chaîne. Ce qui nous donne:

// Fic est un TFileStream créé plus haut dans le code procedure TForm1.SauveRec(Rec: TMonRec); var I: integer; begin I := length(Rec.Nom); Fic.Write(I, sizeof(I)); Fic.Write(Rec.Nom[1], I); end; procedure TForm1.LitRec(var Rec: TMonRec); var I: integer; begin Fic.Read(I, sizeof(I)); SetLength(Rec.Nom, I); // allocation suffisante de place FicRead(Rec.Nom[1], I); Fic.Read(Rec.Age, sizeof(Rec.Age)); end;
Simple non ?


2. Créer son propre composant de sauvegarde


Pour créer un code réutilisable, vous pouvez faire comme moi, et créer un descendant de TFileStream avec des fonctions supplémentaires. Voici le code utilisé:

unit EnhFileStream; interface uses Windows, Messages, SysUtils, Classes; type TEnhFileStream = class(TFileStream) private { Déclarations privées } protected { Déclarations protégées } public procedure SauveChaine(Chaine: string); function LitChaine: string; published { Déclarations publiées } end; implementation { TEnhFileStream } function TEnhFileStream.LitChaine: string; var I: integer; begin Read(I, sizeof(I)); result := ''; SetLength(result, I); Read(result[1], I); end; procedure TEnhFileStream.SauveChaine(Chaine: string); var I: integer; begin I := length(Chaine); Write(I, sizeof(I)); Write(Chaine[1], I); end; end.
De cette façon, vous n'avez plus à vous soucier de l'écriture. La sauvegarde sera par exemple:

procedure TForm1.SauveRec(Rec: TMonRec); begin Fic := TEnhFileStream.Create(FicName, fmCreate or fmOpenWrite); Fic.SauveChaine(Rec.Nom); Fic.Write(Rec.Age, sizeof(Rec.Age)); Fic.Free; end;
Bien sur, ne vous empèche de rajouter votre procédure selon les types utilisés. L'ajoût de SauveMonRec(Rec: TMonRec) vous permettra un appel direct d'I/O dans votre code source. Vous ajouterez une facilité de maintenance supplémentaire.

procedure TEnhFileStream.SauveMonRec(Rec: TMonRec); var I: integer; begin I := length(Rec.Nom); Write(I, sizeof(I)); Write(Rec.Nom[1], I); Write(Rec.Age, sizeof(Rec.Age)); end;
et le tour est joué. Dans votre code source, il vous suffira d'un

Fic.SauveMonRec(MonRec);
pour sauver votre record dans un fichier.

A bientôt pour d'autres articles ....



Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
Responsables bénévoles de la rubrique Delphi : Nono40 et Pedro - Contacter par EMail :
Vos questions techniques : forum d'entraide Delphi - Publiez vos articles, tutoriels et cours
et rejoignez-nous dans l'équipe de rédaction du club d'entraide des développeurs francophones
Nous contacter - Copyright © 2000-2008 www.developpez.com - Legal informations.