There are two separate sets of stream classes, one for streams on collections, and one for streams on files.
Streams on collections are instances of ReadStream, WriteStream, or ReadWriteStream, which are subclasses of PositionableStream, which is a subclass of Stream. The other subclass of Stream is EsRandom.
Streams on files are instances of ReadFileStream, WriteFileStream, or ReadWriteFileStream, which are subclasses of FileStream.
The two stream hierarchies are not physically related, as was shown in the table of contents for this chapter, but they work on the same principles and respond to almost the same set of messages, differing only in the kinds of objects that can be used, and differences due to the objects, files, or collections, over which the stream operates.
All streams respond to three messages.
atEnd Has the final item been consumed?
do: Iterate through a stream
next Answer the next item in the stream
These are the only messages that random number streams
answer to.
Streams provide an abstraction that enables store and retrieve operations to be performed on the elements of an underlying composite data structure, usually an indexed collection. In particular, a stream remembers the position of the last element in the data structure to be read or written. Streams are positionable, that is, the current read/write position can be changed using the stream's public interface. This allows elements to be accessed in either a sequential or non-sequential fashion.
It is implicit in stream semantics that the elements of the underlying data structure are sequentially ordered. With respect to the Common Language Data Type classes, a ReadWriteStream or WriteStream can stream over an Array, a String, or a DBString(characters in the range 0 to 65535, that is, double-byte (DB) charaters). A ReadStream can stream over the same set of classes plus OrderedCollection, SortedCollection, or Symbol.
Positionable streams have a collection across which they stream, starting with the first element and continuing to the last, unless explicitly changed. Streams keep an internal position for the previous element. Positionable streams can be opened on any kind of indexed collection.
atEnd Has the final item been consumed?
close Close the stream
contents Answer the whole contents of the stream
isEmpty Is the stream empty?
position Get the current position of the stream
position: n Set the current position of the stream
reset Set the position to the front of the collection
setToEnd Set the position to the end of the collection
size Answer size of file in bytes
skip: n Skip the next n elements
upToEnd Answer all items up to the stream's end
ReadStream on: 'Mary had a little laptop, its screen was white as snow'
Items are taken (read) from the stream with these messages:
copyFrom:to: Copy into new collection
do: Iterate through a stream; never terminates
next Answer the next element
next: n Answer the next n elements as a collection
nextLine Answer up to the next line delimiter
nextMatchFor: anObjec Answer all elements up to anObject
peek Peek at next element
peekFor: Compare next element with an object
skip: n Skip n items
skipTo: item Skip items to the next occurrence of item
skipToAll: aCollection Skip items to aCollection
upTo: item Return items up to next occurrence of item
upToAll: aCollection Skip items up to aCollection
upToEnd Get elements up to the end
The next message is used when the stream needs to be processed one item at a time.
- To get the next character from a read stream:
| rs |
rs := ReadStream on: 'Mary had a little laptop, its screen was white as
snow'.
^rs next
Answers: $M
- To get the next word from a read stream:
| rs |
rs := ReadStream on: 'Mary had a little laptop, its screen was white as snow'.
^rs upTo: $ "Get up to the next blank"
Answers: 'Mary'
- To get the next line from a read stream:
| rs |
rs := ReadStream on: 'Mary had a little laptop,',LineDelimiter,'its screen was white as snow'.
^rs nextLine
Answers: 'Mary had a little laptop,'
WriteStream on: String new
or:
WriteStream on: Array new
The output collection will be automatically extended when it gets full. Items are written to the stream with messages such as:
flush Flush buffers to disk
nextPut: item Put a single item
nextPutAll: collection Put a collection
next: n put: item Put n copies of item
cr Put a line delimiter
space Put a space
tab Put a tab
The next example writes two lines to a stream, and then uses the contents message to retrieve a copy of the elements written to the stream.
- Simple Write Stream
| ws |
ws := WriteStream on: String new.
ws nextPutAll: 'Mary had a little laptop,'; cr.
ws nextPutAll: 'it''s screen was white as snow;'; cr.
ws nextPutAll: 'and everywhere that Mary went;'; cr.
ws nextPutAll: 'her laptop was sure to go.'; cr.
^ ws contents
- Answers:
'Mary had a little laptop,
it's screen was white as snow;
and everywhere that Mary went,
her laptop was sure to go.'
ReadWriteStream answers to the same messages as ReadStream and WriteStream, as well as the truncating message:
copyFrom:to: Copy into new collection
cr Put a line delimiter
do: Iterate through a stream; never terminates
flush Flush buffers to disk
next Answer the next element
next: n Answer the next n elements as a collection
nextLine Answer up to the next line delimiter
nextMatchFor: anObject Answer all elements up to anObject
nextPut: item Put a single item
nextPutAll: collection Put a collection
next: n put: item Put n copies of item
peek Peek at next element
peekFor: Compare next element with an object
skip: n Skip n items
skipTo: item Skip items to the next occurrence of item
skipToAll: aCollection Skip items to aCollection
space Put a space
tab Put a tab
truncate truncate objects?
upTo: item Return items up to next occurrence of item
upToAll: aCollection Skip items up to aCollection
upToEnd Get elements up to the end
Random streams answer to these messages:
atEnd Always answers false
do: Iterate through a stream; never terminates
next Answer the next random number
As a Random Stream example, shuffle a 'deck' of 'cards' using random numbers.
The cards are represented by integers from 1 to 52 in an ordered collection;
removal is random. Removed cards are placed into a second array.
Shuffle a deck of cards
| deck rand shuf n |
rand := EsRandom new. Get new generator.
deck := (1 to: 52) as OrderedCollection. Init deck to integers 1 to 52
shuf := OrderedCollection new: deck size. Put shuffled cards here
deck size timesRepeat: [ Loop on initial deck size
n := (deck size * rand next)
floor asInteger + 1. Rand range 1 to cur deck size
shuf addLast: (deck removeAtIndex: n) ]. Move card to shuffled deck
shuf size = 52 ifFalse:
[self error: 'This better not ever happen!' ].
^shuf
A string representing the current line delimiter can be obtained by sending the lineDelimiter message to a file stream instance, and can be changed to an arbitrary string by sending the lineDelimiter: message. This makes it possible to adopt a single platform's file convention as the standard for all platforms, or to use nextLine to read files written on other platforms.
The line delimiters can be changed in various ways to match other supported platforms (by name) or to other sequences. The line delimiters are in the pool dictionary CldtConstants. The default is named LineDelimiter.
Line delimiters are set with the lineDelimiter: message to a stream.
The two examples below are the same, provided that the default line delimiter has not been changed.
aStream cr. "Output a line delimiter"
aStream nextPutAll: LineDelimiter "Output a line delimiter"
The next example demonstrates the use of the lineDelimiter: message, as well as their effect on the cr message:
To set to the default line delimiter:
aStream lineDelimiter: LineDelimiter.
cr; nextPutAll: '<-default line delimiter'.
There are three kinds of file streams:
ReadFileStream Read files only
WriteFileStream Write files only
ReadWriteFileStream Reads and write files
The open: message opens an existing file, while the openEmpty: message truncates an existing file (to size 0) or creates the file if it does not exist.
Generally, open: is used when reading files and openEmpty: is used when writing new files or overwriting existing files.
Here is an example that illustrates how to open an existing file for reading and properly check for open errors.
Open existing file for reading; check for open errors
| file |
(file := ReadFileStream open: 'existing.txt') isError
ifTrue: [ ^self error: file message ].
"...use the file stream for reading..."
file close. "when done, close the file stream."
Here is an example that illustrates how to open an existing file for reading
and writing and properly check for open errors.
Open existing file for reading and writing, or creates the file
if it doesn't exist; check for open errors.
| file |
(file := ReadWriteFileStream open: 'existing.txt') isError
ifTrue: [ ^self error: file message ].
"...use the file stream for reading and/or writing..."
file close.
Here is an example that illustrates how to open an existing file for writing
only, using the openEmpty: message, and properly check for open errors.
Open file for writing (empty it if it exists); check for open
errors.
| file |
(file := WriteFileStream openEmpty: 'new.txt') isError
ifTrue: [ ^self error: file message ].
"...use the file stream for writing..."
file close
Once a file stream is open, it is operated upon with the same set
of messages used for streams, including, for reading:
next Answer the next element
next: n Answer the next n elements as a collection
nextLine Answer up to the next line delimiter
nextMatchFor: anObject Answer all elements up to anObject
skip: n Skip n items
skipTo: item Skip items to the next occurrence of item
skipToAll: aCollection Skip items to aCollection
upTo: item Return items up to next occurrences of item
upToAll: aCollection Skip items up to aCollection
and for writing:
nextPut: item Put a single item
nextPutAll: collection Put a collection
next: n put: item Put n copies of item
cr Put a line delimiter
space Put a space
tab Put a tab
Once all desired operations have been performed, the file stream instance
must be closed by sending it the close message before it is discarded. This
closes the file, by deallocating any operating system resources associated
with the file stream, and flushing any cached data to disk.On double-byte platforms, the platform character encoding does not necessarily match the character encoding used within Smalltalk. As a result, Smalltalk strings must be converted to and from the platform representation as they are written to and read from files. When the Smalltalk and platform encodings differ, the stream answered by the open: and openEmpty: messages will not be an instance of the class to which the message was sent. In such cases the open: and openEmpty: messages answer a specialized stream that conforms to the requested protocols and manages the conversion of Smalltalk strings to the appropriate platform representation.
In these cases, it is important to use the isError message to test the result of the open: and openEmpty: operations rather than testing the class of the returned object.
Simple Write File Stream
| ws text |
(ws := ReadWriteFileStream openEmpty: 'maryslap.txt') isError
ifTrue: [ ^self error: ws message ].
ws nextPutAll: 'Mary had a little lamp,'; cr.
ws nextPutAll: 'it''s light was white as show;'; cr.
ws nextPutAll: 'and everywhere that Mary went;'; cr.
ws nextPutAll: 'that lamp was sure to glow.'; cr.
text := ws contents.
ws close.
^ text
-Answers:
'Mary had a little lamp,
it's light was white as snow;
and everywhere that Mary went,
that lamp was sure to glow.'
The next example shows how to open an existing file for reading, another for
writing, and then write the input file's entire contents to the output
file. After two opens, the expression old contents reads the whole
contents of the open file old into memory; then nextPutAll:
outputs it all to new.
Copy a file all at once; prompt for file names
| old new |
(old := ReadFileStream
open: (CxFileSelectionPrompter new prompt)) isError
ifTrue: [ ^self error: old message ].
(new := WriteFileStream
openEmpty: (System prompt:'Output file name')) isError
ifTrue: [^self error: new message ].
new nextPutAll: old contents.
old close.
new close.
The next example reads a text file and changes the first lower case
character which follows a period, possibly with separating white
space, into an upper case character. It will take text such as:
the dog ran up the tree. the cat barked. the boy laughed.
and convert it to:
The dog ran up the tree. The cat barked. The boy laughed.
Capitalize first letter past a period in a file.
| input output str period |
period := true.
str := CwFileSelectionPrompter new title: 'Select input file';
(input := ReadFileStream open: str) isError
ifTrue: [ ^self error: input message ].
str := System prompt: 'Enter name of output file'.
(output := WriteFileStream open: str) isError
ifTrue: [ ^self error: output message ].
[input atEnd ]
whileFalse: [ |char|
char := input next.
period & char isLowercase
ifTure: [ output nextPut: char asUppercase ]
ifFalse: [output nextPut: char ].
char isSeparator
ifFalse: [ period := char == $.] ].
input close.
output close.
Go to Glossary
Return to Chapter7: Collections
Return to Main Page