I/O in the twenty-first century – knowing streams
Many of the I/O related activities handle streams of data. A stream is a sequence of data elements made available over time. According to Wikipedia:
A stream can be thought of as a conveyor belt that allows items to be processed one at a time rather than in large batches.
At the lowest level, all the streams are bytes, but using a high-level interface could obviously help the programmer to handle his data. This is the reason why a stream object usually has methods such as read
, seek
, write
, and many more, just to make the handling of byte stream a bit simpler.
In this recipe, we'll see some streams utilization examples.
Getting ready
In the good old Pascal days, there was a set of functions to handle the I/O (AssignFile
, Reset
, Rewrite
, CloseFile
, and so on), now we've a bounce of classes. All Delphi streams inherit from TStream
and can be used as an internal stream of one of the adapter classes (as adapter, I mean an implementation of the Adapter or Wrapper design pattern from the famous Gang of Four, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Addison-Wesley Professional, book about design patterns).
There are 10 fundamental types of streams:
Class |
Use |
---|---|
|
This is a writer for binary data |
|
This is a writer for characters to stream |
|
This is a writer for a string |
|
This is a writer for sequence of characters; it is an abstract class |
|
This writes component data to an associated stream |
|
This reads component data from an associated stream |
|
This is a reader for a stream of characters |
|
This is a reader for a string |
|
This is a reader for sequence of characters; it is an abstract class |
|
This is a reader for binary data |
You can check the complete list and their intended use directly on the Embarcadero website at http://docwiki.embarcadero.com/RADStudio/XE6/en/Streams,_Reader_and_Writers.
As Joel Spolsky (http://www.joelonsoftware.com/articles/Unicode.html) says, "You can no longer pretend that plaintext is ASCII", so while we write streams, we've to pay attention to which encoding our text has and which encoding our counterpart is waiting for. One of the most frequent necessities is to efficiently read and write a text file using the correct encoding.
"The Single Most Important Fact About Encodings" It does not make sense to have a string without knowing what encoding it uses. You can no longer stick your head in the sand and pretend that "plain" text is ASCII. | ||
--Joel Spolsky |
The point Joel is making is that the content of a string doesn't know about the type of character encoding it uses.
When you think about file handling, ask yourself: could this file become 10 MB? And 100 MB? 1 GB? How will my program behave in that case? Handling a file one line at time and not loading all the files contents in memory is usually a good insurance for these cases. A stream of data is a good way to do this kind of thing. In this recipe, we'll see the practical utilization of streams, stream writers, and streams readers.
How to do it…
The project is not complex, all the interesting stuff happens in the btnWriteFile
and btnReadFile
files.
To write the file, we use TStreamWriter
. The TStreamWriter
class (as its counterpart TStreamReader
) is a wrapper for a TStream
descendent and adds some useful high-level methods to write to the stream. There are a lot of overloaded methods (Write
/WriteLine
) to allow an easy writing to the underlying stream. However, you can access the underlying stream using the BaseStream
property of the wrapper. Just after writing the file, the memo reloads the file using the same encoding used to write it and shows it. This is only a fast check for this recipe, you don't need the TMemo
component at all in your real project. The btnReadFile
file simply opens the file using a stream and passes the stream to a TStreamReader
that, using the right encoding, reads the file one line at time.
Now, let's do some checks. Run the program and with the encoding set to ASCII, click on btnWriteFile. The memo will show garbage text, as shown in the following screenshot. This is because we are using the wrong encoding for the data we are writing in the file.
Now select UTF8 from the RadioGroup and retry. Clicking on btnWriteFile, you will see the correct text in the memo. Try to change the Current Encoding setting using ASCII and click on btnReadFile. You will still get garbage text. Why? Because the file has been read with the wrong encoding. You have to know the encoding before safely reading the file contents. To read the text that we wrote, we have to use the very same encoding. Play with the other encodings to see different behaviors.
There's more...
Streams are very powerful and their uniform interface helps us to write portable and generic code. With the help of streams and polymorphism, we can write code that uses a TStream
component to do some work without knowing which kind of stream it is!
Also, a lesser known possibility, if you ever write a program that needs to access to the good-old STD
_INPUT
, STD_OUTPUT
, or STD_ERROR
, you can use THandleStream
to wrap these system handles to a nice TStream
interface with the following code:
program StdInputOutputError; //the following directive instructs the compiler to create a //console application and not a GUI one, which is the default. {$APPTYPE CONSOLE} uses System.Classes, // required for Stream classes Winapi.Windows; // required to have access to the STD_* handles var StdInput: TStreamReader; StdOutput, StrError: TStreamWriter; begin StdInput := TStreamReader.Create( THandleStream.Create(STD_INPUT_HANDLE)); StdInput.OwnStream; StdOutput := TStreamWriter.Create( THandleStream.Create(STD_OUTPUT_HANDLE)); StdOutput.OwnStream; StdError := TStreamWriter.Create( THandleStream.Create(STD_ERROR_HANDLE)); StdError.OwnStream; { HERE WE CAN USE OURS STREAMS } // Let's copy a line of text from STD_IN to STD_OUT StdOutput.writeln(StdInput.ReadLine); { END - HERE WE CAN USE OURS STREAMS } StdError.Free; StdOutput.Free; StdInput.Free; end.
Moreover, when you work with file-related streams, the TFile
class (contained in System.IOUtils.pas
) is very useful, and it has some helper methods to write shorter and more readable code.