Back: Inheritance and Polymorphism
Up: Tutorial
Forward: The output stream
 
Top: GNU Smalltalk User's Guide
Contents: Table of Contents
Index: Class index
About: About this document

5.10 Smalltalk Streams

Our examples have used a mechanism extensively, even though we haven't discussed it yet. The Stream class provides a framework for a number of data structures, including input and output functionality, queues, and endless sources of dynamically-generated data. A Smalltalk stream is quite similar to the UNIX streams you've used from C. A stream provides a sequential view to an underlying resource; as you read or write elements, the stream position advances until you finally reach the end of the underlying medium. Most streams also allow you to set the current position, providing random access to the medium.

5.10.1 The Output Stream  Which, even though you maybe didn't know it, we've used all the time
5.10.2 Your Own Stream  Which, instead, is something new
5.10.3 Files  Which are streams too
5.10.4 Dynamic Strings  A useful application of Streams


5.10.1 The Output Stream

The examples in this book all work because they write their output to the Transcript stream. Each class implements the printOn: method, and writes its output to the supplied stream. The printNl method all objects use is simply to send the current object a printOn: message whose argument is Transcript (by default attached to the standard output stream found in the stdout global). You can invoke the standard output stream directly:
 
   'Hello, world' printOn: stdout !
   stdout inspect !

or you can do the same for the Transcript, which is yet another stream:
 
   'Hello, world' printOn: stdout !
   Transcript inspect !

the last inspect statement will show you how the Transcript is linked to stdout(32).


5.10.2 Your Own Stream

Unlike a pipe you might create in C, the underlying storage of a Stream is under your control. Thus, a Stream can provide an anonymous buffer of data, but it can also provide a stream-like interpretation to an existing array of data. Consider this example:
 
   Smalltalk at: #a put: (Array new: 10) !
   a at: 4 put: 1234 !
   a at: 9 put: 5678 !
   Smalltalk at: #s put: (ReadWriteStream on: a) !
   s inspect !
   s position: 1 !
   s inspect !
   s nextPut: 11; nextPut: 22 !
   (a at: 1) printNl !
   a do: [:x| x printNl] !
   s position: 2 !
   s do: [:x| x printNl] !
   s position: 5 !
   s do: [:x| x printNl] !
   s inspect !

The key is the on: message; it tells a stream class to create itself in terms of the existing storage. Because of polymorphism, the object specified by on: does not have to be an Array; any object which responds to numeric at: messages can be used. If you happen to have the NiledArray class still loaded from the previous chapter, you might try streaming over that kind of array instead.

You're wondering if you're stuck with having to know how much data will be queued in a Stream at the time you create the stream. If you use the right class of stream, the answer is no. A ReadStream provides read-only access to an existing collection. You will receive an error if you try to write to it. If you try to read off the end of the stream, you will also get an error.

By contrast, WriteStream and ReadWriteStream (used in our example) will tell the underlying collection to grow when you write off the end of the existing collection. Thus, if you want to write several strings, and don't want to add up their lengths yourself:

 
   Smalltalk at: #s put: (ReadWriteStream on: (String new)) !
   s inspect !
   s nextPutAll: 'Hello, '!
   s inspect !
   s nextPutAll: 'world'!
   s inspect !
   s position: 1 !
   s inspect !
   s do: [:c | stdout nextPut: c ] !
   (s contents) printNl !

In this case, we have used a String as the collection for the Stream. The printOn: messages add bytes to the initially empty string. Once we've added the data, you can continue to treat the data as a stream. Alternatively, you can ask the stream to return to you the underlying object. After that, you can use the object (a String, in this example) using its own access methods.

There are many amenities available on a stream object. You can ask if there's more to read with atEnd. You can query the position with position, and set it with position:. You can see what will be read next with peek, and you can read the next element with next.

In the writing direction, you can write an element with nextPut:. You don't need to worry about objects doing a printOn: with your stream as a destination; this operation ends up as a sequence of nextPut: operations to your stream. If you have a collection of things to write, you can use nextPutAll: with the collection as an argument; each member of the collection will be written onto the stream. If you want to write an object to the stream several times, you can use next:put:, like this:

 
   Smalltalk at: #s put: (ReadWriteStream on: (Array new: 0)) !
   s next: 4 put: 'Hi!' !
   s position: 1 !
   s do: [:x | x printNl] !


5.10.3 Files

Streams can also operate on files. If you wanted to dump the file `/etc/passwd' to your terminal, you could create a stream on the file, and then stream over its contents:
 
   Smalltalk at: #f put: (FileStream
       open: '/etc/passwd'
       mode: FileStream read) !
   f do: [ :c | Transcript nextPut: c ] !
   f position: 30 !
   25 timesRepeat: [ Transcript nextPut: (f next) ] !
   f close !

and, of course, you can load Smalltalk source code into your image:
 
   FileStream fileIn: '/users/myself/src/source.st' !


5.10.4 Dynamic Strings

Streams provide a powerful abstraction for a number of data structures. Concepts like current position, writing the next position, and changing the way you view a data structure when convenient combine to let you write compact, powerful code. The last example is taken from the actual Smalltalk source code--it shows a general method for making an object print itself onto a string.

 
   printString
       | stream |
       stream := WriteStream on: (String new).
       self printOn: stream.
       ^stream contents
   !

This method, residing in Object, is inherited by every class in Smalltalk. The first line creates a WriteStream which stores on a String whose length is currently 0 (String new simply creates an empty string. It then invokes the current object with printOn:. As the object prints itself to "stream", the String grows to accommodate new characters. When the object is done printing, the method simply returns the underlying string.

As we've written code, the assumption has been that printOn: would go to the terminal. But replacing a stream to a file like `/dev/tty' with a stream to a data structure (String new) works just as well. The last line tells the Stream to return its underlying collection, which will be the string which has had all the printing added to it. The result is that the printString message returns an object of the String class whose contents are the printed representation of the very object receiving the message.




This document was generated on May, 12 2002 using texi2html