![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
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.
6.10.1 The Output Stream Which, even though you maybe didn't know it, we've used all the time 6.10.2 Your Own Stream Which, instead, is something new 6.10.3 Files Which are streams too 6.10.4 Dynamic Strings A useful application of Streams
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
(40).
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:
a := Array new: 10 a at: 4 put: 1234 a at: 9 put: 5678 s := 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:
s := 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 |
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:
s := ReadWriteStream on: (Array new: 0) s next: 4 put: 'Hi!' s position: 1 s do: [:x | x printNl] |
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:
f := FileStream open: '/etc/passwd' mode: FileStream read f linesDo: [ :c | Transcript nextPutAll: c; nl ] 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' |
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.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |