|
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 la
développement de projets pour
ses clients, le conseil 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, Ado.Net, Asp.Net et UML qu'il
anime personellement tous les mois, à Paris, en province ou sur site client.
|