5.8. System.IO: Classes to Read and Write Streams of Data

 < Day Day Up > 

The System.IO namespace contains the primary classes used to move and process streams of data. The data source may be in the form of text strings, as discussed in this chapter, or raw bytes of data coming from a network or device on an I/O port. Classes derived from the Stream class work with raw bytes; those derived from the TexTReader and TextWriter classes operate with characters and text strings (see Figure 5-6). We'll begin the discussion with the Stream class and look at how its derived classes are used to manipulate byte streams of data. Then, we'll examine how data in a more structured text format is handled using the TexTReader and TextWriter classes.

Figure 5-6. Selected System.IO classes


The Stream Class

This class defines the generic members for working with raw byte streams. Its purpose is to abstract data into a stream of bytes independent of any underlying data devices. This frees the programmer to focus on the data stream rather than device characteristics. The class members support three fundamental areas of operation: reading, writing, and seeking (identifying the current byte position within a stream). Table 5-10 summarizes some of its important members. Not included are methods for asynchronous I/O, a topic covered in Chapter 13, "Asynchronous Programming and Multithreading."

Table 5-10. Selected Stream Members

Member

Description

 CanRead CanSeek CanWrite 

Indicates whether the stream supports reading, seeking, or writing.

Length

Length of stream in bytes; returns long type.

Position

Gets or sets the position within the current stream; has long type.

Close()

Closes the current stream and releases resources associated with it.

Flush()

Flushes data in buffers to the underlying device for example, a file.

 Read(byte array, offset, count) ReadByte() 

Reads a sequence of bytes from the stream and advances the position within the stream to the number of bytes read. ReadByte reads one byte. Read returns number of bytes read; ReadByte returns 1 if at end of the stream.

SetLength()

Sets the length of the current stream. It can be used to extend or truncate a stream.

Seek()

Sets the position within the current stream.

 Write(byte array, offset, count) WriteByte() 

Writes a sequence of bytes (Write) or one byte (WriteByte) to the current stream. Neither has a return value.


These methods and properties provide the bulk of the functionality for the FileStream, MemoryStream, and BufferedStream classes, which we examine next.

FileStreams

A FileStream object is created to process a stream of bytes associated with a backing store a term used to refer to any storage medium such as disk or memory. The following code segment demonstrates how it is used for reading and writing bytes:

 try {    // Create FileStream object    FileStream fs = new FileStream(@"c:\artists\log.txt",          FileMode.OpenOrCreate, FileAccess.ReadWrite);    byte[] alpha = new byte[6] {65,66,67,68,69,70}; //ABCDEF    // Write array of bytes to a file    // Equivalent to: fs.Write(alpha,0, alpha.Length);    foreach (byte b in alpha) {       fs.WriteByte(b);}    // Read bytes from file    fs.Position = 0;         // Move to beginning of file    for (int i = 0; i< fs.Length; i++)       Console.Write((char) fs.ReadByte()); //ABCDEF    fs.Close(); catch(Exception ex) {    Console.Write(ex.Message); } 

As this example illustrates, a stream is essentially a byte array with an internal pointer that marks a current location in the stream. The ReadByte and WriteByte methods process stream bytes in sequence. The Position property moves the internal pointer to any position in the stream. By opening the FileStream for ReadWrite, the program can intermix reading and writing without closing the file.

Creating a FileStream

The FileStream class has several constructors. The most useful ones accept the path of the file being associated with the object and optional parameters that define file mode, access rights, and sharing rights. The possible values for these parameters are shown in Figure 5-7.

Figure 5-7. Options for FileStream constructors


The FileMode enumeration designates how the operating system is to open the file and where to position the file pointer for subsequent reading or writing. Table 5-11 is worth noting because you will see the enumeration used by several classes in the System.IO namespace.

Table 5-11. FileMode Enumeration Values

Value

Description

Append

Opens an existing file or creates a new one. Writing begins at the end of the file.

Create

Creates a new file. An existing file is overwritten.

CreateNew

Creates a new file. An exception is thrown if the file already exists.

Open

Opens an existing file.

OpenOrCreate

Opens a file if it exists; otherwise, creates a new one.

truncate

Opens an existing file, removes its contents, and positions the file pointer to the beginning of the file.


The FileAccess enumeration defines how the current FileStream may access the file; FileShare defines how file streams in other processes may access it. For example, FileShare.Read permits multiple file streams to be created that can simultaneously read the same file.

MemoryStreams

As the name suggests, this class is used to stream bytes to and from memory as a substitute for a temporary external physical store. To demonstrate, here is an example that copies a file. It reads the original file into a memory stream and then writes this to a FileStream using the WriteTo method:

 FileStream fsIn = new FileStream(@"c:\manet.bmp",               FileMode.Open, FileAccess.Read); FileStream fsOut = new FileStream(@"c:\manetcopy.bmp",               FileMode.OpenOrCreate, FileAccess.Write); MemoryStream ms = new MemoryStream(); // Input image byte-by-byte and store in memory stream int imgByte; while ((imgByte = fsIn.ReadByte())!=-1){    ms.WriteByte((byte)imgByte); } ms.WriteTo(fsOut);              // Copy image from memory to disk byte[] imgArray = ms.ToArray(); // Convert to array of bytes fsIn.Close(); fsOut.Close(); ms.Close(); 

BufferedStreams

One way to improve I/O performance is to limit the number of reads and writes to an external device particularly when small amounts of data are involved. Buffers have long offered a solution for collecting small amounts of data into larger amounts that could then be sent more efficiently to a device. The BufferedStream object contains a buffer that performs this role for an underlying stream. You create the object by passing an existing stream object to its constructor. The BufferedStream then performs the I/O operations, and when the buffer is full or closed, its contents are flushed to the underlying stream. By default, the BufferedStream maintains a buffer size of 4096 bytes, but passing a size parameter to the constructor can change this.

Buffers are commonly used to improve performance when reading bytes from an I/O port or network. Here is an example that associates a BufferedStream with an underlying FileStream. The heart of the code consists of a loop in which FillBytes (simulating an I/O device) is called to return an array of bytes. These bytes are written to a buffer rather than directly to the file. When fileBuffer is closed, any remaining bytes are flushed to the FileStream fsOut1. A write operation to the physical device then occurs.

 private void SaveStream() {    Stream fsOut1 = new FileStream(@"c:\captured.txt",       FileMode.OpenOrCreate, FileAccess.Write);    BufferedStream fileBuffer = new BufferedStream(fsOut1);    byte[] buff;         // Array to hold bytes written to buffer    bool readMore=true;    while(readMore) {       buff = FillBytes();         // Get array of bytes       for (int j = 0;j<buff[16];j++){         fileBuffer.WriteByte(buff[j]);   // Store bytes in buffer       }       if(buff[16]< 16) readMore=false;   // Indicates no more data    }    fileBuffer.Close();  // Flushes all remaining buffer content    fsOut1.Close();      // Must close after bufferedstream } // Method to simulate I/O device receiving data private static byte[] FillBytes() {    Random rand = new Random();    byte[] r = new Byte[17];    // Store random numbers to return in array    for (int j=0;j<16;j++) {       r[j]= (byte) rand.Next();       if(r[j]==171)        // Arbitrary end of stream value       {          r[16]=(byte)(j);  // Number of bytes in array          return r;       }    }    System.Threading.Thread.Sleep(500);  // Delay 500ms    return r; } 

Using StreamReader and StreamWriter to Read and Write Lines of Text

Unlike the Stream derived classes, StreamWriter and StreamReader are designed to work with text rather than raw bytes. The abstract TextWriter and TexTReader classes from which they derive define methods for reading and writing text as lines of characters. Keep in mind that these methods rely on a FileStream object underneath to perform the actual data transfer.

Writing to a Text File

StreamWriter writes text using its Write and WriteLine methods. Note their differences:

  • WriteLine works only with strings and automatically appends a newline (carriage return\linefeed).

  • Write does not append a newline character and can write strings as well as the textual representation of any basic data type (int32, single, and so on) to the text stream.

The StreamWriter object is created using one of several constructors:

Syntax (partial list):

 public StreamWriter(string path) public StreamWriter(stream s) public StreamWriter(string path, bool append) public StreamWriter(string path, bool append, Encoding encoding) 

Parameters:

path

Path and name of file to be opened.

s

Previously created Stream object typically a FileStream.

append

Set to true to append data to file; false overwrites.

encoding

Specifies how characters are encoded as they are written to a file. The default is UTF-8 (UCS Transformation Format) that stores characters in the minimum number of bytes required.


This example creates a StreamWriter object from a FileStream and writes two lines of text to the associated file:

 string filePath = @"c:\cup.txt"; // Could use: StreamWriter sw = new StreamWriter(filePath); // Use FileStream to create StreamWriter FileStream fs = new FileStream(filePath, FileMode.OpenOrCreate,                 FileAccess.ReadWrite); StreamWriter sw2 = new StreamWriter(fs); // Now that it is created, write to the file sw2.WriteLine("The world is a cup"); sw2.WriteLine("brimming\nwith water."); sw2.Close();  // Free resources 

Reading from a Text File

A StreamReader object is used to read text from a file. Much like StreamWriter, an instance of it can be created from an underlying Stream object, and it can include an encoding specification parameter. When it is created, it has several methods for reading and viewing character data (see Table 5-12).

Table 5-12. Selected StreamReader Methods

Member

Description

Peek()

Returns the next available character without moving the position of the reader. Returns an int value of the character or 1 if none exists.

 Read() Read(char buff, int ndx,     int count) 

Reads next character (Read()) from a stream or reads next count characters into a character array beginning at ndx.

ReadLine()

Returns a string comprising one line of text.

ReadToEnd()

Reads all characters from the current position to the end of the Textreader. Useful for downloading a small text file over a network stream.


This code creates a StreamReader object by passing an explicit FileStream object to the constructor. The FileStream is used later to reposition the reader to the beginning of the file.

 String path= @"c:\cup.txt"; if(File.Exists(path)) {    FileStream fs = new FileStream(path,          FileMode.OpenOrCreate, FileAccess.ReadWrite);    StreamReader reader = new StreamReader(fs);    // or StreamReader reader = new StreamReader(path);    // (1) Read first line    string line = reader.ReadLine();    // (2) Read four bytes on next line    char[] buff  = new char[4];    int count = reader.Read(buff,0,buff.Length);    // (3) Read to end of file    string cup = reader.ReadToEnd();    // (4) Reposition to beginning of file    //     Could also use reader.BaseStream.Position = 0;    fs.Position = 0;    // (5) Read from first line to end of file    line = null;    while ((line = reader.ReadLine()) != null){       Console.WriteLine(line);    }    reader.Close(); } 

Core Note

A StreamReader has an underlying FileStream even if it is not created with an explicit one. It is accessed by the BaseStream property and can be used to reposition the reader within the stream using its Seek method. This example moves the reader to the beginning of a file:

 reader.BaseStream.Seek(0, SeekOrigin.Begin); 


StringWriter and StringReader

These two classes do not require a lot of discussion, because they are so similar in practice to the StreamWriter and StreamReader. The main difference is that these streams are stored in memory, rather than in a file. The following example should be self-explanatory:

 StringWriter writer = new StringWriter(); writer.WriteLine("Today I have returned,"); writer.WriteLine("after long months "); writer.Write("that seemed like centuries"); writer.Write(writer.NewLine); writer.Close(); // Read String just written from memory string myString = writer.ToString(); StringReader reader = new StringReader(myString); string line = null; while ((line = reader.ReadLine()) !=null) {    Console.WriteLine(line); } reader.Close(); 

The most interesting aspect of the StringWriter is that it is implemented underneath as a StringBuilder object. In fact, StringWriter has a GetStringBuilder method that can be used to retrieve it:

 StringWriter writer = new StringWriter(); writer.WriteLine("Today I have returned,"); // Get underlying StringBuilder StringBuilder sb = writer.GetStringBuilder(); sb.Append("after long months "); Console.WriteLine(sb.ToString()); writer.Close(); 

Core Recommendation

Use the StringWriter and StringBuilder classes to work with large strings in memory. A typical approach is to use the StreamReader.ReadToEnd method to load a text file into memory where it can be written to the StringWriter and manipulated by the StringBuilder.


Encryption with the CryptoStream Class

An advantage of using streams is the ability to layer them to add functionality. We saw earlier how the BufferedStream class performs I/O on top of an underlying FileStream. Another class that can be layered on a base stream is the CryptoStream class that enables data in the underlying stream to be encrypted and decrypted. This section describes how to use this class in conjunction with the StreamWriter and StreamReader classes to read and write encrypted text in a FileStream. Figure 5-8 shows how each class is composed from the underlying class.

Figure 5-8. Layering streams for encryption/decryption


CryptoStream is located in the System.Security.Cryptography namespace. It is quite simple to use, requiring only a couple of lines of code to apply it to a stream. The .NET Framework provides multiple cryptography algorithms that can be used with this class. Later, you may want to investigate the merits of these algorithms, but for now, our interest is in how to use them with the CryptoStream class.

Two techniques are used to encrypt data: assymmetric (or public key) and symmetric (or private key). Public key is referred to as asymmetric because a public key is used to decrypt data, while a different private key is used to encrypt it. Symmetric uses the same private key for both purposes. In our example, we are going to use a private key algorithm. The .NET Framework Class Library contains four classes that implement symmetric algorithms:

  • DESCryptoServiceProvider Digital Encryption Standard (DES) algorithm

  • RC2CryptoServiceProvider RC2 algorithm

  • RijndaelManaged Rijndael algorithm

  • TRippleDESCryptoServiceProvider TRipleDES algorithm

We use the DES algorithm in our example, but we could have chosen any of the others because implementation details are identical. First, an instance of the class is created. Then, its key and IV (Initialization Vector) properties are set to the same key value. DES requires these to be 8 bytes; other algorithms require different lengths. Of course, the key is used to encrypt and decrypt data. The IV ensures that repeated text is not encrypted identically. After the DES object is created, it is passed as an argument to the constructor of the CryptoStream class. The CryptoStream object simply treats the object encapsulating the algorithm as a black box.

The example shown here includes two methods: one to encrypt and write data to a file stream, and the other to decrypt the same data while reading it back. The encryption is performed by WriteEncrypt, which receives a FileStream object parameter encapsulating the output file and a second parameter containing the message to be encrypted; ReadEncrypt receives a FileStream representing the file to be read.

 fs = new FileStream("C:\\test.txt", FileMode.Create,                     FileAccess.Write); MyApp.WriteEncrypt(fs, "Selected site is in Italy."); fs= new FileStream("C:\\test.txt",FileMode.Open,                    FileAccess.Read); string msg = MyApp.ReadEncrypt(fs); Console.WriteLine(msg); fs.Close(); 

WriteEncrypt encrypts the message and writes it to the file stream using a StreamWriter object that serves as a wrapper for a CrytpoStream object. CryptoStream has a lone constructor that accepts the file stream, an object encapsulating the DES algorithm logic, and an enumeration specifying its mode.

 // Encrypt FileStream private static void WriteEncrypt(FileStream fs, string msg) {    // (1) Create Data Encryption Standard (DES) object    DESCryptoServiceProvider crypt = new          DESCryptoServiceProvider();    // (2) Create a key and Initialization Vector     // requires 8 bytes    crypt.Key = new byte[] {71,72,83,84,85,96,97,78};    crypt.IV  = new byte[] {71,72,83,84,85,96,97,78};    // (3) Create CryptoStream stream object    CryptoStream cs = new CryptoStream(fs,       crypt.CreateEncryptor(),CryptoStreamMode.Write);    // (4) Create StreamWriter using CryptoStream    StreamWriter sw = new StreamWriter(cs);    sw.Write(msg);    sw.Close();    cs.Close(); } 

ReadEncrypt reverses the actions of WriteEncrypt. It decodes the data in the file stream and returns the data as a string object. To do this, it layers a CryptoStream stream on top of the FileStream to perform decryption. It then creates a StreamReader from the CryptoStream stream that actually reads the data from the stream.

 // Read and decrypt a file stream. private static string ReadEncrypt(FileStream fs) {    // (1) Create Data Encryption Standard (DES) object    DESCryptoServiceProvider crypt =          new DESCryptoServiceProvider();    // (2) Create a key and Initialization Vector    crypt.Key = new byte[] {71,72,83,84,85,96,97,78};    crypt.IV  = new byte[] {71,72,83,84,85,96,97,78};    // (3) Create CryptoStream stream object    CryptoStream cs = new CryptoStream(fs,          crypt.CreateDecryptor(),CryptoStreamMode.Read);    // (4) Create StreamReader using CryptoStream    StreamReader sr = new StreamReader(cs);    string msg = sr.ReadToEnd();    sr.Close();    cs.Close();    return msg; } 

     < Day Day Up > 


    Core C# and  .NET
    Core C# and .NET
    ISBN: 131472275
    EAN: N/A
    Year: 2005
    Pages: 219

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net