{{tag>cours dev poo pharo}}
====== Présentation des objets de type Stream ======
Dans cette séquence nous allons parler de la bibliothèque de Stream qui est fournie de base avec Pharo. Nous verrons ce que sont les streams et comment les utiliser, dans quels cas ils peuvent être utiles.
Alors qu'est-ce qu'un stream ? Un stream, c'est un objet qui permet d'itérer sur une séquence d'éléments. Cette séquence, ça peut être:
* une collection en mémoire;
* un flux réseau;
* un fichier;
* etc.
Un stream garde en mémoire la position courante, et au fur à mesure de l'utilisation du stream on va pouvoir avancer ou reculer pour pouvoir lire ou écrire des éléments dans le stream.
===== Aperçu de l'API des Streams =====
==== Création ====
Pour créer un stream, il y a quelques objets sur lesquels on peut utiliser les messages ''**readStream**'' et ''**writeStream**'' c'est notamment le cas des fichiers ou les collections.
Le message ''**streamContents:**'' s'envoie à une collection et prend un block en paramètre qui reçoit un stream. L'utilisation de ce stream au sein du block va créer une collection qui sera finalement retournée par la méthode. On verra l'utilisation de cette méthode-là à la fin.
Les messages ''**readStream**'', soit ''**writeStream**'', soit ''**readWriteStream**'' envoyés à une classe permettent de créer une nouvelle instance par rapport à une collection.
==== Lire les éléments ====
Les 3 messages:
* ''**next**''
* ''**upTo: anObject**''
* ''**upToEnd**''
Permettent de lire des éléments, donc un ou plusieurs jusqu'à une certaine limite.
==== Écrire des éléments ====
* ''**nextPut: anElement**''
* ''**nextPutAll: aCollection**''
Permettent d'écrire un élément ou une collection d'éléments dans le stream.
Alors voici quelques exemples permettant de lire dans un Stream. Première étape, on crée un stream. Ici, on crée un stream en lecture à partir d'une collection.
|stream|
stream := #( $a $b $c $d $e $f ) readStream.
"A l'initialisation le curseur est à la position 0"
"le message next permet de récupérer le premier élément du flux"
stream next.
"> $a"
"Après le premier appel à next le curseur a avancé d'un élément"
stream position.
"> 1"
stream upTo: $d.
"> #($b $c)"
stream position.
"> 4"
stream upToEnd.
"> #($e $f)"
La collection contient les caractères de l'alphabet entre 'a' et 'f'. On créé un ''Stream'' sur cette collection-là (ligne 2) et on va regarder les caractères un par un.
Une fois qu'on a notre Stream, la première chose qu'on peut faire, c'est envoyer le message next (ligne 6) qui va nous retourner l'élément juste après la position courante.
Dès l'initialisation du stream, la position courante c'est 0 donc on se situe au début du stream, ''next'' retourne au début le premier élément, c'est-à-dire le caractère 'a'.
Si j'envoie successivement ''next'' à l'objet stream je vais obtenir 'b', puis 'c', puis 'd', etc en faisant évoluer la position du curseur d'un cran à chaque fois.
Dans l'exemple j'envoie le message ''upTo:'' et un élément (ligne 14). La méthode retourne tous les éléments entre la position courante et l'élément que j'ai passé en paramètre. Donc là si avant j'avais 'a', je me situe en position 1 et si j'envoie le message ''upTo: $d'', il va me retourner tout ce qu'il y a entre la position courante et 'd', c'est-à-dire une collection contenant les éléments 'b' et 'c'. 'd' est consommé par le stream, c'est-à-dire que maintenant le stream se situe juste après 'd' mais 'd' n'est pas retourné.
Le message unaire ''position'' nous donne la position en cours (lignes 11 et 17). La position du curseur commence à 0. À ce moment-là si j'envoie le message ''upToEnd'' au stream (ligne 20), je vais obtenir une collection avec tous les éléments qui se situent entre la position courante et la fin du stream, c'est-à-dire 'e' et 'f'.
Donc on voit que le Stream maintient une position courante que je peux faire avancer grâce à quelques méthodes.
De la même façon on peut écrire dans un stream. La première étape encore une fois, c'est créer le stream.
|myStream|
myStream := (Array new: 6) writeStream.
myStream nextPut: 1;
contents.
"> #(1)"
myStream nextPutAll: #( 4 8 2 6 7 );
contents.
"> #(1 4 8 2 6 7)"
''(Array new: 6)'' me permet de créer un tableau vide mais de taille 6. J'envoie le message ''writeStream'' dessus pour créer un stream sur ce tableau-là, de façon à pouvoir remplir le tableau petit à petit grâce à mon Stream.
Je stocke ce stream dans la variable ''myStream'' et je commence par envoyer le message ''nextPut: 1'', qui prend un élément et ajoute cet élément en position courante dans le stream.
Maintenant, mon tableau contient un 1 suivi de 5 cases vides. Le message ''nextPutAll:'', quant à lui, prend une collection d'objets à mettre les uns après les autres dans le stream. Après l'envoi de ce message ''nextPutAll:'' à mon stream, j'obtiens le tableau qui contient un résultat de mon ''nextPut:'' précédent suivi de 4, 8, 2, 6, 7, résultats du nextPutAll: (ligne 11).
Donc les streams sont particulièrement utiles et efficaces pour lire et écrire dans les collections d'objets.
===== Écrire dans un fichier =====
Je peux aussi lire et écrire dans des fichiers. Là, je montre un exemple de comment écrire dans un nouveau fichier qui n'existe pas encore.
|fileStream|
fileStream := 'hello.txt' asFileReference writeStream.
fileStream nextPutAll: 'Hello Pharo!';
close.
Dans la chaîne de caractères ici j'indique le nom du fichier. En envoyant à une chaîne de caractères le message ''asFileReference'', je créé une référence vers un fichier. Là c'est un fichier qui n'existe pas encore mais je peux quand même avoir une référence dessus. J'ai une référence vers un fichier 'hello.txt' et j'envoie le message ''writeStream'' sur ce fichier histoire d'avoir un stream en écriture vers ce fichier qui n'existe pas encore.
Dès qu'on va écrire dans le stream, le fichier va être créé. Maintenant que j'ai mon stream, j'envoie le message ''nextPutAll:'' avec une chaîne de caractères et ce message ''nextPutAll:'' va écrire caractère après caractère chaque élément de ma chaîne de caractères. Il va écrire le 'H', puis le 'e', puis le 'l', etc. A la fin, je ferme mon stream pour indiquer au système d'exploitation que j'ai fini d'écrire dans le fichier, et qu'il peut lui écrire l'ensemble sur le support de stockage et fermer le pointeur sur le fichier.
===== Lire un fichier =====
Maintenant que j'ai écrit dans ce fichier, je peux avoir envie de le lire.
|fileStream|
fileStream := 'hello.txt' asFileReference readStream.
fileStream next.
"> $H"
fileStream upToEnd.
"> 'ello Pharo!'"
De la même façon, j'ai le nom du fichier sous forme d'une chaînes de caractères, le message ''asFileReference'' qui me permet de créer une référence vers ce fichier-là, et ''readStream'' qui me permet d'ouvrir un flux en lecture.
Avec next, je récupère le premier élément dans le stream. J'avais écrit "Hello Pharo", le premier élément, c'est 'H'. Et avec le message ''upToEnd:'', je récupère tous les caractères entre la position courante, c'est-à-dire juste après le 'H' et avant le 'e' et jusqu'à la fin du stream, jusqu'à la fin du fichier: j'obtiens 'ello Pharo!' sans le 'H' puisque je l'ai déjà récupéré grâce au ''next'' précédent.
===== Créer une collection à partir d'un stream =====
On peut créer des collections en utilisant des streams. Ça c'est très utile quand on veut créer des collections et qu'il nous faut du code pour choisir ce qu'on va mettre dans la collection petit à petit.
myStream := OrderedCollection new writeStream.
myStream nextPut: 1;
contents.
"Equivalent "
OrderedCollection
streamContents: [ :stream | stream nextPut: 1 ]
Là, je veux créer une OrderedCollection en envoyant des messages à un stream. À partir de ma classe OrderedCollection, je fais une nouvelle instance, j'en fais un flux en écriture et avec le message ''nextPut:'', j'ajoute l'élément 1 dans mon stream.
Maintenant quand je vais envoyer le message ''contents'' (ligne 4), je vais obtenir une instance de la classe OrderedCollection qui contient uniquement la valeur 1.
Ces 3 expressions peuvent s'écrire plus simplement comme on le voit en-dessous avec l'équivalent présenté lignes 7 et 8. En envoyant le message ''streamContents:'' à la classe de collection qui nous intéresse, donc ici c'est OrderedCollection, j'envoie ce message "streamContents" à ''OrderedCollection'', je lui passe en paramètre un block qui prend un stream en paramètre. Au sein de ce block, je vais utiliser le stream pour remplir ma collection petit à petit et quand le block se termine j'obtiens ma collection.
Dans le block, je fais ''stream nextPut: 1'', j'ajoute 1 dans mon stream qui va l'ajouter dans la collection. Quand ''streamContents'' se termine, je vais obtenir une OrderedCollection qui contient 1.
StreamContents c'est utile pour créer des collections à partir de rien.
===== Résumé =====
Dans cette séquence, nous avons découvert l'API de stream. Il y a énormément de méthodes dans cette API que je vous invite à aller découvrir en naviguant dans les classes grâce au navigateur de codes Nautilus.
Un stream peut lire et écrire dans des collections d'éléments en mémoire, dans des fichiers, sur le réseau et d'autres éléments encore.
Un stream a toujours une position courante, c'est important. La position courante sépare les éléments du passé des éléments du futur, et en fait évoluer cette position courante à chaque fois qu'on va vouloir lire ou écrire dans le flux. Enfin, les streams peuvent servir à créer des nouvelles collections.