Recherche
1 connecté

  Actuellement : 2 commentaires Les Streams en Delphi

Les Streams en Delphi
les bases

Introduction

    A quoi servent les streams? Ils sont utilisés afin de traiter des flots de données qui peuvent arriver / être stockés n'importe où: sur le disque dur, en mémoire, à partir d'un réseau... Dans cet article seront surtout traités les streams concernant les accès au disque dur et à la mémoire. Ceci vous permettra de lire et écrire très facilement dans un fichier, ou de manipuler des "grandes variables". Contrairement aux variables habituelles, les streams permettent de manipuler facilement des flots de données d'une taille de l'ordre du Mégaoctet. Il n'y a en fait pas de limite de taille. De plus les streams sont très rapides, ce qui permet de remplacer les "file of byte" extrèmements lents, par exemple.

    Si vous trouvez des erreurs plus ou moins graves, si vous pensez qu'il manque certaines remarques importantes, que certains conseils devraient figurer dans cette page, ou s'il y a vraiment trop de fautes d'orthographe, n'hésitez pas à poster un message sur le forum ou à m'envoyer un mail!

Principe et utilité des streams

    Prenons un exemple. Vous ouvrez un fichier de 400Mo avec un stream concernant l'accès aux fichiers. En fait, aucun octet du fichier ne sera mis en mémoire; le stream aura simplement ouvert le fichier. Vous pourrez vous déplacer dans le fichier pour aller à n'importe quel octet, copier une petite partie (disons 500Ko), aller ailleur et recopier 1Mo... Vous pourrez aussi modifier le fichier ouvert, puisqu'il pourra être ouvert en lecture et en écriture à la fois. Pour les streams concernant un accès à la mémoire c'est le même principe sauf que les données se trouvent dans... la mémoire.

    Tous les streams sont "compatibles", c'est à dire que vous pouvez par exemple utiliser un stream accédant à un fichier pour le recopier, entièrement ou en partie, dans un stream mémoire (ou vice versa). Ceci est très utile pour accélérer l'accès au fichier: vous recopiez un gros bloc de ce fichier et le traitez en mémoire, ce qui est beaucoup plus rapide que de traiter le fichier directement.

Le type TStream

    C'est le stream de base; en fait, ce n'est jamais lui que l'on utilise, mais il contient toutes les méthodes et propriétés utiles pour un stream. Seuls quelques détails diffèrent d'un type TStream à l'autre, ce qui donnera naissance à des descendants des TStream, comme TFileStream (stream d'accès à un fichier) ou TMemoryStream (stream d'accès à un bloc mémoire). Tous ces descendants contiennent bien sûr les méthodes du type TStream. Celles-ci sont très bien documentées dans l'aide, je vais donc n'en retenir que les principales:

Méthodes:

  • Create: c'est le constructor du stream, comme son nom l'indique. Il crée le stream, et il diffère selon le type de stream.
  • Read: permet de lire une partie du stream.
  • Write: permet d'écrire dans le stream.
  • Seek: permet d'aller à un certain endroit du stream.
  • CopyFrom: permet de copier un morceau de stream dans un autre.
  • Free: libère le stream de la mémoire.

Propriétés:

  • Position: indique la position de la lecture dans le stream.
  • Size: indique la taille du stream.

    D'une manière générale, on commence par créer le stream avec Create, on lit ou on écrit avec Read, Write, Seek et Position, et on ferme le stream avec Free. On peut utiliser CopyFrom pour effectuer des copies d'un stream à un autre.

    Lorsqu'un travaille avec les streams, les propriétés Write, Read et CopyFrom écrivent et lisent toujours à partir de Position. On modifie généralement Position en utilisant Seek. Il faut donc toujours être sûr que l'on est au bon endroit dans un stream avant d'y effectuer un travail quelconque. De plus, Read, Write et CopyFrom modifient la Position. Par exemple, si vous utiliser Read pour lire 4 octets, la position sera décalée de 4 octets plus loins dans le stream. Vous pourrez alors lire les 4 octets suivants. Si vous voulez relire les mêmes octets, il faudra revenir à la position précédente. Même chose pour CopyFrom; si vous copiez un stream dans un autre, et que vous voulez travailler dans ce nouveau stream, il faudra penser à revenir en arrière dans le stream pour pouvoir y lire des données.

    Size est automatiquement mise à jour si vous écrivez des données dans le stream avec Write ou CopyFrom. Si vous écrivez des données au milieu du stream, les données précédentes se trouvant à l'endroit de l'écriture seront écrasées.

Syntaxe des méthodes

function Read(var Buffer; Count: Longint): Longint;

    Cette fonction lit une partie du stream et la place dans une variable. Cette variable peut être n'importe quoi - un record, un integer... Mais attention, les classes, les strings et tout ce qui est pointeur en général, ne pourra pas être lu correctement. Pour cela il faudra procéder autrement. Dans Count, vous mettez le nombre d'octets à lire - par exemple, pour un integer, mettez 4. Pour un byte, mettez 1.
    Read renvoie le nombre d'octets réellements lus par la fonction. Vous pouvez ainsi contrôler que l'opération s'est déroulée correctement.
    Pensez à régler Position si vous voulez lire à un endroit précis du stream. Read modifie Position en le déplaçant du nombre d'octets lus; ainsi si vous voulez lire plusieurs morceaux du streams placés à la suite, il n'est pas nécessaire de régler Position entre les lectures.

function Write(const Buffer; Count: Longint): Longint;

    Cette fonction écrit des données dans le stream. Comme pour Read, on peut écrire des record, integer etc, mais pas directement des classes, strings ou pointeurs. Vous placez la variable à écrire dans Buffer et le nombre d'octets à écrire dans Count. Vous pouvez écrire des integer en spécifiant leur vraie taille (qui est de 4), mais vous pouvez aussi choisir d'écrire seulement sur 1 octet, dans ce cas l'integer par exemple, sera écrit comme un byte. Si sa valeur est supérieure à 255 elle sera donc perdue.
    Comme pour Read, vous devez penser à régler Position avant de l'utiliser, et la fonction renvoie le nombre d'octets réellement écrits.

function Seek(Offset: Longint; Origin: Word): Longint;

    Cette fonction modifie Position en se déplaçant dans le stream. Vous spécifiez Origin pour donner le type de déplacement:
    - soFromBeginning vous permet de spécifier directement la position que vous voulez dans Offset, qui doit donc être positif.
    - soFromCurrent vous permet de rechercher à partir de la position actuelle; si vous mettez -5 dans Offset par exemple, vous irez 5 octets en arrière; si vous mettez 10, vous irez 10 octets en avant...
    - soFromEnd vous permet de spécifier une position par rapport à la fin du fichier. Offset doit donc être négatif ou nul. Si vous mettez 0, vous irez à la fin du fichier. Si vous mettez -5, vous irez 5 octets avant la fin du fichier.

function CopyFrom(Source: TStream; Count: Longint): Longint;

    Cette fonction copie dans le stream une partie des données contenues dans un autre stream. Cet autre stream est donné dans le paramètre Source, et le nombre d'octet à copier est donné dans Count. La fonction renvoie le nombre d'octets réellements copiés; vous pouvez contrôler ce nombre afin de vérifier que tout s'est bien déroulé. Vous pouvez aussi passer 0 dans Count pour copier tout le contenu de Source. Dans ce cas, il n'est pas nécessaire de régler la position dans Source avant la copie. Sinon, la copie se fait à partir de Source.Position; il faut penser à régler cette propriété avant d'effectuer la copie.

Le type TMemoryStream

    C'est le stream le plus simple. Il permet de réserver un gros bloc mémoire (ou un petit...) puis d'y travailler en toute sérénité. Il faut cependant faire attention à ne pas utiliser toute la mémoire.

    Vous pouvez l'utiliser pour y stocker un fichier, puis l'éditer; si le fichier est trop gros, il faudra le lire Mégaoctet par Mégaoctet, par exemple.

    Ce stream contient deux méthodes spécifiques, qui permettent de charger le stream à partir... d'un stream, ou d'un fichier. Ces méthodes s'appellent LoadFromStream et LoadFromFile. LoadFromStream est un moyen de copier entièrement un stream, du début à la fin, dans le TMemoryStream. Attention, le TMemoryStream est vidé auparavant. Vous n'avez pas besoin de régler les Position des deux streams. LoadFromFile vide aussi le contenu, puis charge le stream en le remplissant d'un fichier, le nom de ce fichier étant passé en paramètre. C'est comme si vous utilisiez LoadFromStream à partir d'un TFileStream. C'est très pratique, mais ça ne permet pas de couper le fichier en plusieurs morceaux et / ou de n'en prendre qu'une partie... pour ça il faut utiliser CopyFrom.

Le type TFileStream

    C'est un stream très utile. Il permet de lire et écrire dans un fichier, existant ou non. Sa simplicité et sa relative rapidité (je crois qu'il y a légèrement mieux question vitesse, mais c'est compensé par la simplicité d'utilisation) vous donneront sûrement envie de l'utiliser pour remplacer les fichiers typés, comme les "file of byte" extrèmements lents avec les gros fichiers. Vous pourrez aussi les utiliser pour copier très facilement et rapidement un bout de fichier. Par exemple, imaginons que vous ayez un fichier "archive", qui contient une table des matières, puis pleins de fichiers dans un seul fichier. Avec le TFileStream, vous pourrez lire la table des matières pour trouver un fichier précis, puis aller directement au fichier pour le copier, sans perdre de temps à lire les fichiers qui ne vous intéressent pas.

    La seule différence, et pas la moindre, par rapport au TStream de base, est qu'il contient une méthode Create modifiée pour savoir quel fichier ouvrir et comment (lecture / écriture, droits d'accès). Il ne contient par contre pas les méthodes LoadFromFile et LoadFromStream du TMemoryStream, ce qui est logique puisque ce stream accède directement au disque dur et n'utilise donc pas vraiment la mémoire (sauf pour stocker le handle du stream et d'autres infos la position dans le fichier, quoi).

constructor Create(const FileName: string; Mode: Word);

    FileName contient le nom du fichier ouvert.

    Mode contient le mode d'ouverture du fichier. Il y a deux questions à se poser:

    - Qu'est-ce que vous voulez faire du fichier? Vous pouvez le créer avec le paramètre fmCreate (si le fichier existe déjà, il sera ouvert en écriture). Vous pouvez le lire avec fmOpenRead (dans ce cas, vous ne pourrez rien y écrire). Vous pouvez l'ouvrir en écriture avec fmOpenWrite (l'équivalent de fmCreate sauf qu'il faut obligatoirement que le fichier existe). Et enfin, vous pouvez vouloir modifier certaines parties du fichier, en fonction ou non de son contenu actuel: pour cela, utilisez fmOpenReadWrite. Vous pourrez à la fois lire et écrire dans le fichier. Attention, si une application a déjà ouvert le fichier, certains de ces modes seront sans doutes inaccessibles.

    - Est-ce que d'autres applications peuvent accéder au fichier? Si non, utilisez fmShareExclusive. Aucune application ne pourra ni lire, ni écrire dans le fichier. fmShareDenyWrite permet d'empêcher (deny = interdire) aux autres applications l'écriture (Write) dans le fichier. fmShareDenyRead permet de façon analogue d'interdire la lecture. Quant à fmShareDenyNone, il n'interdit rien du tout. En général, si vous ouvrez un fichier en lecture (fmOpenRead), vous autoriserez la lecture (fmShareDenyWrite); si vous ouvrez un fichier en écriture (fmCreate, fmOpenWrite ou fmOpenReadWrite), vous n'autoriserez rien (fmShareExclusive). Autoriser l'écriture, c'est risquer que le fichier soit modifié alors que vous être en train de l'utiliser. Il existe un dernier mode, fmShareCompat, mais je ne sais pas à quoi il sert.
    Comme Mode contient deux informations différentes, il faut les combiner avec l'opérateur "or". Par exemple: MonFileStream.Create('fichier.txt', fmCreate or fmShareExclusive); permet de créer le fichier 'fichier.txt', en n'autorisant ni la lecture, ni l'écriture.

Les autres types de streams

    L'aide de Delphi montre qu'il existe d'autres types de streams, que je n'ai malheureusement pas vraiment utilisés. On trouve les TStringStream, qui, d'après l'aide de Delphi, sont utilisés pour manipuler des chaînes (strings) en mémoire (je paraphrase un max là); les TBlobStream, pour utiliser des champs Blob; les TWinSocketStream, utilisés lors de connexions réseaux par sockets, mais je n'ai jamais réussi à m'en servir; les TResourceStream, permettant d'accéder aux ressources de l'application (contenues dans les .res avant la compilation) et enfin les TOleStream utilisable pour des liaisons OLE (interface COM). A vous de consulter l'aide pour comprendre comment tout ça fonctionne :)

Conclusion

    Vous avez donc pu avoir un aperçu des possibilités offertes par les Streams. En gros, dès que vous avez des données d'une taille importante à traiter, utilisez les streams. De nombreux types permettent de les utiliser; par exemple, un TBitmap possède une méthode LoadFromFile pour ouvrir un bitmap à partir d'un fichier, mais aussi une méthode LoadFromStream pour ouvrir à partir d'un stream (qui doit quand même contenir une image au format bitmap). Ainsi vous pourrez placer dans un stream un fichier, qui aura pu être "prélevé" dans un fichier archive qui contient d'autres fichiers, puis l'utiliser avec ce genre de fonctions. Plus de méthodes de ce genre seront traitées dans un prochain article.