Reading Text Files
Reading files is quite simple, since Microsoft have provided a large number of
classes that represent streams, which may be used to transfer data. Transferring
data here can include such things as reading and writing to files, or downloading
from the Internet, or simply moving data from one location to another using a
stream.
The available classes are all derived from the class System.IO.Stream, which
can represent any stream, and the various classes represent different specializations,
for example streams that are specifically geared to reading or writing to files.
In general, for the examples in this chapter that involve using streams, there
are potentially a number of different ways to write the code, using any of several
of the available stream objects. However, in this chapter we'll just present
one way that you could perform each of the processes of reading and writing to
text and binary files.
Reading text files is quite simple - for this we're going to use the StreamReader
class. The StreamReader represents a stream specifically geared to reading text.
We'll demonstrate the process with a sample that reads in and displays the contents
of the ReadMe.txt file generated by the developer environment's AppWizard for
our earlier EnumFiles sample. The code looks like this.
File fIn = new File ("C:\\dotNET Projects\\Namespaces\\EnumFiles\\ReadMe.txt"); StreamReader strm = fIn.OpenText(); // continue reading until end of file string sLine; do { sLine = strm.ReadLine(); AddItem(sLine); } while (sLine != null); strm.Close();
We obtain a StreamReader instance using the OpenText() method
of the File class. The StreamReader class contains several methods that either
read or peek at differing amounts of data.
Peeking means looking ahead at the data, but without actually moving through
the data. The best way to understand this is by imagining a pointer that indicates
which bit of the file you are due to read next. If you read data, then the pointer
will be moved to point at the byte following the last byte read, so the next
read (or peek) will bring in the next block of data. If you peek at data, the
pointer is not changed, so the next read (or peek) will retrieve the same data
again.
The most useful method however for our purposes is ReadLine(), which reads as
far as the next carriage return, returning the result in a string. If we have
reached the end of the file, ReadLine() does not throw an exception, but simply
returns a null reference - so we use this to test for the end of the file. Note
that a null reference is not the same as an empty string. If we'd instead applied
the condition
while (sLine != "");
to the do loop, the loop would have finished the moment we
came to a blank line in the file, not at the end of the file. (StreamReader.ReadLine()
returns the string without the trailing carriage return and line feed).
Running this sample produces this output, showing it's correctly read the ReadMe.Txt
file:
Writing Text Files
Writing text files follows similar principles to reading from them - in this
case we use the StreamWriter class.
What makes writing text files even easier than reading them is that the method
we use to write a line of text output followed by a carriage return-line feed,
StreamWriter.WriteLine(), has a number of overloads so we don't necessarily need
to pass it just text. It will accept a string, an object, a Boolean or several
of the numeric types. This can be seen from this code sample, which writes out
some text to the blank file we created earlier, MyNewFile.txt.
StreamWriter strm = new StreamWriter ("C:\\dotNET Book\\MyNewFile.txt", false); strm.WriteLine("This is some text"); strm.WriteLine("Next lines are numbers"); strm.WriteLine(3); strm.WriteLine(4.55); strm.WriteLine("And the next line is a boolean"); strm.WriteLine(true); strm.Close();
The results of this can be seen in Notepad:
There are a number of overrides to the constructor of the
StreamWriter. The constructor we have picked in our code sample is quite flexible,
taking two parameters: the full name of the file, and a Boolean that indicates
whether data should be appended to the file. If this is false then the contents
of the file will be overwritten by the StreamWriter. In either case, the file
will be opened if it already exists or created if it doesn't. This behavior can
be customized by using other more complex constructors to the StreamWriter.
Reading Binary Files
Once we get to binary files we need to abandon the text-specific StreamReader
and StreamWriter classes in place of a more general-purpose class. There are
actually two classes that will do the job, System.IO.Stream and System.IO.FileStream.
FileStream is designed specifically for reading and writing to files, while Stream
is able to transmit data between files or other objects. The two classes work
in very similar ways, and for the sake of demonstrating both of them, we'll use
Stream for reading data and then use FileStream in the following sample which
writes data to a file.
This sample demonstrates how to use the Stream class to read data. It opens a
file and reads it, a byte at a time, each time displaying the numeric value of
the byte read.
File fl = new File("C:\\dotNET Book\\TestReader.txt"); Stream strm = fl.OpenRead(); int iNext; do { iNext = strm.ReadByte(); if (iNext != -1) AddItem(iNext.ToString()); } while (iNext != -1); strm.Close();
We obtain an instance of the Stream class by first instantiating
a File object attached to the required file and calling its OpenRead() method.
We then read through the file by calling the Stream.ReadByte() method. This method
reads the next byte returning its value as an int. If we have reached the end
of the file, then -1 is returned, but no exception is thrown - and it is this
condition we use to test for the end of the file. Note that the Stream class
also has a Read() method which can read a specified number of bytes in one go
- I've chosen to use ReadByte() here as it leads to simpler code.
The file I've tested this code on looks like this - the first few letters of
the alphabet followed by three carriage return-line feeds. Although the code
will work on any file, I've demonstrated it on a text file because that makes
it easier to see visually that the results are correct.
Running the code on this file produces the following:
Writing Binary Files
Although we can use either the Stream or FileStream classes to perform this task,
we'll use an instance of FileStream for this sample. This code writes out a short
text file that contains the letters FGHIJK followed by a carriage return-line
feed combination. Note that again although this code is capable of writing any
data, we're using textual data in the sample so that we can easily use Notepad
to check that the sample has worked.
The code looks like this
byte [] bytes = {70,71,72,73,74,75,13,10}; FileStream strm = new FileStream ("C:\\dotNET Book\\TestWriter.txt", FileMode.OpenOrCreate, FileAccess.Write); foreach (byte bNext in bytes) { strm.WriteByte(bNext); } strm.Close();
We first define an array of bytes that contains the data to
be written to the file - in this case the ASCII codes of the characters. True
binary data would have simply meant changing some of the values in this array
to represent non-printable characters.
Next we instantiate a FileStream object. The constructor we use takes three parameters:
the full pathname of the file, the mode we are using to open it and the access
required. The mode and access merit more consideration -- they are enumerated
values respectively taken from two further classes in the System.IO namespace:
FileMode and FileAccess. The possible values these can take are all self-explanatory.
In the case of the mode the possible values are Append, Create, CreateNew, Open,
OpenOrCreate and Truncate. For the access they are Read, ReadWrite and Write.
Finally we use the WriteByte method of the FileStream object to write out each
byte before closing the file. Again there is a FileStream.Write method, which
can write out a number of bytes at a time and which you may prefer to use. We've
stuck with WriteByte because it makes it clearer what is going on as we loop
through the array.
And finally the test whether this code has worked: After running it, opening
the new file with Notepad gives this:
Comments