Streams

A common programming task is to read data from or write data to files, network sockets, or some other devices. The BCL formalizes this behavior by means of an abstract class Stream (namespace System.IO). Table 5.2 shows some frequently used methods available on this class.

Table 5.2. Some Methods Available on System.IO.Stream
MethodDescription
CanSeekCan you seek to a position within the stream?
CanReadCan the stream be read?
CanWriteCan the stream be written to?
LengthLength of the stream.
PositionCurrent cursor position.
SeekSeek to a position.
ReadRead bytes from the stream.
WriteWrite bytes to the stream.
FlushFlush any buffered data to the underlying device.
CloseClose the stream and release any resources (such as sockets and file handles). Data is flushed before the stream is closed.

The Stream class also offers methods to perform asynchronous reads and writes. Check the SDK documentation for more information.

Why doesn't the table show any method to open a stream? The semantics of opening a stream depends on the underlying device. Stream is just an abstract class. Table 5.3 lists some common classes inherited from Stream.

Table 5.3. Some Common Stream-Based Classes
NameDescription
FileStreamA buffered stream based on a disk file
NetworkStreamAn unbuffered stream based on a socket
BufferedStreamA wrapper class that adds buffering to an existing unbuffered stream
MemoryStreamA stream based on memory

How a stream is created depends on the mechanism provided by the underlying class. For example, a FileStream object can be created using the static methods OpenRead (for reading) or OpenWrite (for writing) that are available on a BCL class File (namespace System.IO). The following code excerpt, for example, opens a file for writing, writes some bytes to the stream, and closes it:

// Project FileStream

public static void Main() {
     // Store ASCII "hello" as bytes
     Byte[] data = new Byte[] {104, 101, 108, 108, 111};

     // Open a new file for writing. Write the data
     using (FileStream fs = File.OpenWrite("Output.Dat")) {
       fs.Write(data, 0, data.Length);
       fs.Close();
     }
}

Note that the FileStream object is scoped within a using block. This is to ensure that the file handle is properly disposed off in case of an error (or after it has been used).

Data Encoding

Stream.Read and Stream.Write let you read and write data as bytes. However, programmers in general prefer dealing with strings to bytes. It would be nice to have a mechanism to convert a string to a byte array and vice versa.

At this point, it is worth reinforcing the differences among bytes, characters, and strings under C# (and under .NET). A C# byte (System.Byte) is a single unsigned byte (8-bit), a C# char (System.Char) is a 2-byte Unicode character, and a C# string (System.String) is an array of Unicode characters.

The BCL provides a number of encoding classes under the System.Text namespace to convert between characters, bytes, and strings using various encoding schemes. These classes include ASCIIEncoding, UnicodeEncoding, UTF7Encoding, and UTF8Encoding to deal with ASCII, Unicode, UTF-7, and UTF-8 encoding schemes, respectively. The namespace System.Text also provides another class called Encoding that exposes the encoding classes as static properties. The following code excerpt demonstrates how to use this class to convert between strings and byte arrays:

// Project Encoding

public static void Main() {
     // Convert ASCII-encoded bytes to UNICODE string
     Byte[] buf = new Byte[] {104, 101, 108, 108, 111};
     String s = Encoding.ASCII.GetString(buf);
     Console.WriteLine(s);

     // Convert the string back as ASCII-encoded bytes
     Byte[] asciiBuf = Encoding.ASCII.GetBytes(s);
     foreach(Byte b in asciiBuf) {
       Console.WriteLine(b);
     }

     // Convert the string to UNICODE-encoded bytes
     Byte[] unicodeBuf = Encoding.Unicode.GetBytes(s);
     foreach(Byte b in unicodeBuf) {
       Console.WriteLine(b);
     }
}

Readers and Writers

Using the encoding classes, it is now possible to convert bytes to strings each time they are read from a FileStream object and to convert strings to bytes just before writing them to the FileStream. It is desirable if you don't have to deal with raw byte input and output at all.

The BCL provides two classes, StreamReader and StreamWriter, to help you. StreamReader reads data from a stream as characters or lines. StreamWriter writes data to a stream as characters or lines. The specific encoding to use can be specified in the constructor. In the following code excerpt, an ASCII line is read from an input file and is saved back as a Unicode line in an output file:

// Project ReaderWriter

public static void Main() {
     // Open the file for reading and read one line
     String sLine;
     using (FileStream fsr = File.OpenRead("Readme.Txt")) {
       using(StreamReader reader =
            new StreamReader(fsr,Encoding.ASCII)) {
          sLine = reader.ReadLine();
          reader.Close();
       }
       fsr.Close();
     }

     // Open a new file for writing. Write line as UNICODE
     using (FileStream fsw = File.OpenWrite("NewReadme.Txt")) {
       using(StreamWriter writer =
            new StreamWriter(fsw, Encoding.Unicode)) {
          writer.WriteLine(sLine);
          writer.Close();
       }
       fsw.Close();
     }
}

Note the use of using statements on StreamReader and StreamWriter objects. Both the classes implement IDisposable and release any unmanaged resources used via Dispose method.

Also note that both the classes define an overloaded constructor that takes as a parameter the filename to open (instead of a stream). If you use this constructor, you don't have to deal with stream objects separately.

StreamReader offers a particular feature that you should be aware of. If you are not sure of the encoding used in the input file, you can instruct the StreamReader object to detect the encoding during its construction. If the detect-encoding parameter is specified as true, the reader object looks at the first three bytes of the stream (called byte order marks [BOM]) to identify the encoding. The class is capable of automatically recognizing UTF-8 and Unicode (little-endian as well as big-endian) encoding schemes if the file starts with the appropriate BOM.

StreamReader and StreamWriter classes are great for character input and output, but what if you want to save and load other basic datatypes such as integer, boolean, float, and so on? To solve this problem, the BCL provides two more classes, BinaryReader and BinaryWriter under the namespace System.IO. These two classes provide methods for reading and writing many base datatypes to the stream. The following code excerpt illustrates the use of these classes. The program writes an integer and a double value to a file and reads it back. For simplicity, I am not wrapping any objects in the using block.

// Project BinaryData

public static void Main() {
     // Open a file for binary write
     FileStream fsw = File.OpenWrite("Output.bin");
     BinaryWriter bw = new BinaryWriter(fsw);

     // write some basic data types to the stream
     int iVal = 10;
     double dVal = 20.5;
     bw.Write(iVal);
     bw.Write(dVal);
     bw.Close();
     fsw.Close();

     // Open the file for reading
     FileStream fsr = File.OpenRead("Output.bin");
     BinaryReader br = new BinaryReader(fsr);

     // read data back
     int iValNew = br.ReadInt32();
     double dValNew = br.ReadDouble();
     br.Close();
     fsr.Close();
     Console.WriteLine("{0}, {1}", iValNew, dValNew);
}

Note that the BinaryWriter class can also be used to write strings. One of the overloaded Write methods on the class takes a string as an argument and writes it out as a stream of bytes (based on the encoding the BinaryWriter class is using).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.109.151