u_c_string_file - John COLIBRI. |
- mots clé:utilitaire - gestion de fichier - fiche de taille variable
- logiciel utilisé: Windows 98, Delphi 5.0
- matériel utilisé: Pentium 500Mhz, 128 M de mémoire
- champ d'application: Delphi 1 à 6 sur Windows, Kylix
- niveau: débutant en Pascal et Delphi
- uses: u_c_basic_file, (u_c_basic_object, u_c_file_name,
u_loaded, u_c_display, u_dir, u_c_log, u_strings,
u_types_constants)
- plan:
1 - Introduction 1.1 Historique
Il est de nombreuses circonstances où nous devons stocker sur disques des données de taille variable. Le cas le plus courant est celui des chaînes de caractères: - des adresses postales
- des répertoires disque
- des listes de mots clés
Le stockage de ces données dans des FILE OF pose le même problème de dimensionnement que les ARRAYs: - soit la taille est calculée trop juste, et certaines données ne pourront être stockées
- soit l'allocation est trop généreuse et une gâchis est fatal.
Pour les chaînes, nous pouvons utiliser un fichier ASCII en plaçant chaque donnée sur une ligne. Les fichiers TEXT permettent l'écriture et la lecture
séquentielle mais pas l'accès direct. Les tStringList permettent l'accès direct, à condition de charger tout le fichier en mémoire. De plus les tStringList sont mal adaptés pour les données binaires à cause de la présence
éventuelle de caractères 0. Reste alors le stockage sur disque de la partie variable en conservant dans un autre fichiers les coordonnées de chacune de ces parties. Parmi les formats possibles:
- stocker dans le fichier fixe la position du début de chaque partie variable, et stocker dans chaque enregistrement variable sa taille et les données
- stocker dans le fichier fixe la position et la taille, et dans le fichier
variable les parties variables uniquement.
- une autre solution serait de ne placer dans le fichier fixe que les positions de départ, la taille étant calculée par différence entre deux enregistrements successifs
Suite à quelques problèmes rencontrés sur des structures trop optimisées, nous souhaitions un format dans lequel: - le fichier variable peut continuer à être lu séquentiellement (sans avoir besoin du fichier fixe)
- les modifications sont possibles (en plaçant une partie variable à un autre endroit que son emplacement séquentiel)
1.2 Le format du fichier variable Pour la solution retenue, le format du fichier variable est la suivante:
- pour chaque partie variable:
- un identificateur Integer permettant de retrouver au besoin le RECORD du fichier fixe auquel appartient cette partie variable
- la taille de la partie variable, sous forme d'Integer
- les octets de la partie variable
- le début du fichier contient 4 octets permettant de gérer une FreeList:
- si une partie variable est remplacée par une partie de taille plus grande, nous plaçons la nouvelle valeur en fin de fichier. Le trou rendu disponible pourrait éventuellement être récupéré par un nouvel
enregistrement. Notez aussi que les 8 octets de prologue permettraient éventuellement le "pinning": si certains fichier ont mémorisé la position de l'enregistrement, nous pouvons remplacer l'enregistrement par la
position du nouvel enregistrement et les programmes ayant une référence vers l'ancienne position ne seraient pas affectés.
- le fait que les 4 premier octets soient utilisés pour la gestion des
trous permet aussi d'utiliser la position 0 pour indiquer une partie variable vide, si c'était nécessaire (le NIL de notre fichier)
Mentionnons que si les 4 octets sont alloués, l'unité actuelle ne gère pas
la liste de trous. Nous ajouterons cette partie lorsque cela deviendra utile. Ce fichier contenant des parties variables est utilisé par d'autres structures:
- des structures mémoire (ARRAY OF Integer, tList etc) qui contiendraient les positions de chaque partie variable. Le fichier aurait une durée de vie limitée à l'application
- un fichier fixe (uniquement utilisé pour cela, ou un champ dans un RECORD d'un FILE OF contenant, entre autres, des parties variables) contenant les positions des parties variables
- un fichier c_file_of qui encapsule les FILE OF
2 - Utilisation 2.1 - Interface L'interface de u_c_string_file est la suivante:
type c_string_file= class(c_basic_file)
public
m_first_free_position: Integer;
m_id: Integer;
Constructor create_string_file(p_name, p_file_name: String); Virtual;
function f_force_create: Boolean; Override;
(*
function f_open: Boolean; Override;
procedure close_file; Override; *)
function f_append_string(p_id: Integer;
p_string: String): Integer;
function f_read_string(p_string_position: Integer): String;
Destructor Destroy; Override;
end; |
La classe hérite de c_basic_file (gestion du nom du fichier, copies, recopies, ouvertures etc.) Pour les données: - m_first_free_position: contient la position du premier trou dans le fichier
(inutilisé dans la version actuelle)
- m_id: champ pour stocker temporairement l'identificateur en mémoire
Et pour les méthodes: - le constructeur appelle le constructeur de c_basic_file
- f_force_create: créé le fichier et écrit les 4 octets du début le la liste des trous
- f_append_string: écrit l'enregistrement variable (l'identificateur, la
taille de la chaîne et les caractères de la chaîne). La fonction retourne la position du PREMIER octet écrit sur disque
- f_read_string: retourne la chaîne lorsque nous fournissons la position du premier octet de l'enregistrement
Notez que: - la gestion de la liste des trous n'est pas mise en oeuvre dans cette version
- seules les chaînes de caractère sont traitées. D'autres types de données de taille variable sont utilisables mais les primitives ne sont pas écrites.
L'utilisation de f_block_read et f_block_write rend cette mise en oeuvre aisée
Pour illustrer l'emploi de cette classe, nous allons présenter trois exemples. 2.2 - Un exemple en mémoire
Voici un premier exemple où les positions des chaînes sont stockées en mémoire dans un tableau:
procedure TForm1.test_in_memory_Click(Sender: TObject);
var l_positions: array[0..2] of Integer;
l_record: Integer; begin
with c_string_file.create_string_file('string_file', 'memory.str') do
begin f_force_create;
close_file; f_open;
l_positions[0]:= f_append_string(1, 'pascal');
l_positions[1]:= f_append_string(2, 'sql');
l_positions[2]:= f_append_string(2, 'midas');
close_file; f_open;
for l_record:= 0 to 2 do
display(f_read_string(l_positions[l_record]));
close_file; Free;
end; // with c_string_file
end; // test_in_memory_Click | 2.3 - Exemple avec un FILE OF
Voici un exemple dans lequel nous stockons un identificateur et une chaîne:
procedure TForm1.file_of_Click(Sender: TObject);
type t_record= record
m_id: Integer;
m_postal_address_position: Integer;
end;
var l_file_of: File Of t_record;
l_record: t_record;
l_c_string_file: c_string_file;
procedure write_address(p_id: Integer; p_address: String);
begin
l_record.m_id:= p_id;
l_record.m_postal_address_position:= l_c_string_file.f_append_string(p_id, p_address);
write(l_file_of, l_record);
end; // write_address
begin // file_of_Click
AssignFile(l_file_of, 'postal.bin');
Rewrite(l_file_of);
CloseFile(l_file_of);
l_c_string_file:= c_string_file.create_string_file('string_file', 'postal.str');
l_c_string_file.f_force_create;
l_c_string_file.close_file;
Reset(l_file_of);
l_c_string_file.f_open;
write_address(11, 'Dupon|5 rue de la Paix|75005 PARIS');
write_address(22, 'Smith|2 Hyde Park|3zP9wU2r London');
write_address(33, 'Malooney|Down Under 33|87z Sydney');
l_c_string_file.close_file;
CloseFile(l_file_of);
Reset(l_file_of);
l_c_string_file.f_open;
while not Eof(l_file_of) do
begin
read(l_file_of, l_record);
with l_record do
display(IntToStr(m_id)+ ' '+ l_c_string_file.f_read_string(m_postal_address_position));
end; // while
l_c_string_file.close_file;
CloseFile(l_file_of);
end; // file_of_Click | 2.4 - Exemple avec un c_file_of
Si nous souhaitons encapsuler la gestion du fichier précédent dans une classe, nous pouvons utiliser un descendant des c_file_of dont
nous créons le RECORD contenant l'entier et la position de la chaîne. Il faut de plus réécrire les procédures d'ouverture, fermeture etc. pour que ces opérations soient réalisées sur les deux fichiers.
Voici la classe encapsulant le FILE OF et le c_string_file:
type t_address_record= Record
m_integer: Integer;
m_address_position: Integer;
end;
c_file_of_address= class(c_file_of)
public
m_address_record: t_address_record;
m_c_string_file: c_string_file;
Constructor create_file_of_address(p_name, p_file_name: String); Virtual;
function f_force_create: Boolean; Override;
function f_open: Boolean; Override;
procedure write_address(p_integer: Integer; p_postal_address: String);
procedure read_address(var pv_integer: Integer; var pv_postal_address: String);
function f_display_record: String; Override;
procedure close_file; Override;
Destructor Destroy; Override;
end; |
Notez la présence du champ m_c_string_file qui correspondant au c_string_file Les procédures de gestion opèrent sur les deux fichiers. A titre d'exemple: - voici la procédure d'ouverture:
function c_file_of_address.f_open: Boolean;
begin
Result:= InHerited f_open and m_c_string_file.f_open;
end; // f_open | - et voici la procédure qui écrit les données
procedure c_file_of_address.write_address(p_integer: Integer; p_postal_address: String);
begin
with m_address_record do
begin m_integer:= p_integer;
m_address_position:= m_c_string_file.f_append_string(p_integer, p_postal_address);
end; // with m_address_record
write_record; end; // write_address |
Muni de cette classe, nous pouvons écrire le programme principal qui créé, écrit et relit les adresses:
procedure TForm1.c_file_of_Click(Sender: TObject);
begin
with c_file_of_address.create_file_of_address('address', 'address_2.bin') do
begin f_force_create;
close_file; f_open;
write_address(11, 'Dupon|5 rue de la Paix|75005 PARIS');
write_address(22, 'Smith|2 Hyde Park|3zP9wU2r London');
write_address(33, 'Malooney|Down Under 33|87z Sydney');
close_file; f_open;
while not f_eof do
begin read_record;
display(f_display_record);
end; close_file;
end; // with c_file_of_address.
end; // c_file_of_Click | 2.5 - Répertoires et Directives de Compilation
L'unité est prévue pour être placée dans: C: programs colibri_helpers classes
Vous pouvez naturellement changer cette organisation par Projet | Options | Directories Les directives de compilation sont: - R+ (vérification des intervalles)
- S+ (vérification de la pile)
- pas d'optimisation
3 - Programmation Rien de particulier. La classe s'appuie essentiellement sur la classe u_c_basic_file. 4 - Améliorations
Il reste essentiellement à terminer l'implémentation des données autres que les String, et la gestion de la liste des trous. 5 - Télécharger le source Vous pouvez télécharger: Avec les mentions d'usage: - j'apprécie tous les commentaires, remarques ou critiques
- signalez-moi les bugs que vous trouverez.
L'auteur John COLIBRI est passionné par le développement Delphi et les applications de Bases de Données. Il a écrit de nombreux livres et articles, et partage son temps entre le développement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n, refactoring) pour ses clients, le
conseil (composants, architecture, test) et la
formation. Son site contient des articles
avec code source, ainsi que le programme et le calendrier des stages de formation Delphi, base de données, programmation objet, Services Web, Tcp/Ip et
UML qu'il anime personellement tous les mois, à Paris, en province ou sur site client. |