Section 21.2. Reading and Writing Data


21.2. Reading and Writing Data

Reading and writing data is accomplished with the Stream class. Remember streams? This is a chapter about streams.[2]

[2] With a tip of the hat to Arlo Guthrie.

Stream supports synchronous and asynchronous reads and writes. The .NET Framework provides a number of classes derived from Stream, including FileStream, MemoryStream, and NetworkStream. In addition, there is a BufferedStream class that provides buffered I/O and can be used with any of the other stream classes. The principal classes involved with I/O are summarized in Table 21-5.

Table 21-5. Principal I/O classes of the .NET Framework

Class

Use

Stream

Abstract class that supports reading and writing bytes.

BinaryReader/BinaryWriter

Read and write encoded strings and primitive datatypes to and from streams.

File, FileInfo, Directory, DirectoryInfo

Provide implementations for the abstract FileSystemInfo classes, including creating, moving, renaming, and deleting files and directories.

FileStream

For reading to and from File objects; supports random access to files. Opens files synchronously by default; supports asynchronous file access.

Textreader,TextWriter, StringReader, StringWriter

TexTReader and TextWriter are abstract classes designed for Unicode character I/O. StringReader and StringWriter write to and from strings, allowing your input and output to be either a stream or a string.

BufferedStream

A stream that adds buffering to another stream such as a NetworkStream. BufferedStreams can improve performance of the stream to which they are attached, but note that FileStream has buffering built in.

MemoryStream

A nonbuffered stream whose encapsulated data is directly accessible in memory, and is most useful as a temporary buffer.

NetworkStream

A stream over a network connection.


21.2.1. Binary Files

This section starts by using the basic Stream class to perform a binary read of a file. The term binary read is used to distinguish from a text read. If you don't know for certain that a file is just text, it is safest to treat it as a stream of bytes, known as a binary file.

The Stream class is chock-a-block with methods, but the most important are Read( ), Write( ), BeginRead( ), BeginWrite( ), and Flush( ). All of these are covered in the next few sections.

To perform a binary read, begin by creating a pair of Stream objects, one for reading and one for writing:

Stream inputStream = File.OpenRead(     @"C:\test\source\test1.cs"); Stream outputStream = File.OpenWrite(     @"C:\test\source\test1.bak");

To open the files to read and write, use the static OpenRead( ) and OpenWrite() methods of the File class. The static overload of these methods takes the path for the file as an argument, as shown previously.

Binary reads work by reading into a buffer. A buffer is just an array of bytes that will hold the data read by the Read( ) method.

Pass in the buffer, the offset in the buffer at which to begin storing the data read in, and the number of bytes to read. InputStream.Read reads bytes from the backing store into the buffer and returns the total number of bytes read.

It continues reading until no more bytes remain:

while ( (bytesRead =     inputStream.Read(buffer,0,SIZE_BUFF)) > 0 ) {     outputStream.Write(buffer,0,bytesRead); }

Each bufferful of bytes is written to the output file. The arguments to Write( ) are the buffer from which to read, the offset into that buffer at which to start reading, and the number of bytes to write. Notice that you write the same number of bytes as you just read.

Example 21-4 provides the complete listing.

Example 21-4. Implementing a binary read and write to a file
#region Using directives using System; using System.Collections.Generic; using System.IO; using System.Text; #endregion namespace ImplementingBinaryReadWriteToFile {    class Tester    {       const int SizeBuff = 1024;       public static void Main( )       {          // make an instance and run it          Tester t = new Tester( );          t.Run( );       }       // Set it running with a directory name       private void Run( )       {          // the file to read from          Stream inputStream = File.OpenRead(             @"C:\test\source\test1.cs" );          // the file to write to          Stream outputStream = File.OpenWrite(             @"C:\test\source\test1.bak" );          // create a buffer to hold the bytes           byte[] buffer = new Byte[SizeBuff];          int bytesRead;          // while the read method returns bytes          // keep writing them to the output stream          while ( ( bytesRead =             inputStream.Read( buffer, 0, SizeBuff ) ) > 0 )          {             outputStream.Write( buffer, 0, bytesRead );          }          // tidy up before exiting          inputStream.Close( );          outputStream.Close( );       }    } }

Before you run this program, create the C:\test\source subdirectory and add a file (containing the source to this program) named test1.cs. As with previous examples, Unix, Linux, and Mac OS X readers should adjust the paths appropriately.


The result of running this program is that a copy of the input file (test1.cs) is made in the same directory and named test1.bak. You can compare these files using your favorite file comparison tool; they are identical, as shown in Figure 21-1.[3]

[3] My personal favorite file comparison utility, as shown here, is ExamDiff Pro (http://www.prestosoft.com/ps.asp?page=edp_examdiffpro).

Figure 21-1. File comparison showing the two files are identical


21.2.2. Buffered Streams

In the previous example, you created a buffer to read into. When you called Read( ), a bufferful was read from disk. It might be, however, that the operating system can be much more efficient if it reads a larger (or smaller) number of bytes at once.

A buffered stream object creates an internal buffer, and reads bytes to and from the backing store in whatever increments it thinks are most efficient. It will still fill your buffer in the increments you dictate, but your buffer is filled from the in-memory buffer, not from the backing store. The net effect is that the input and output are more efficient and thus faster.

A BufferedStream object is composed around an existing Stream object that you already have created. To use a BufferedStream, start by creating a normal stream class as you did in Example 21-4:

Stream inputStream = File.OpenRead(     @"C:\test\source\folder3.cs"); Stream outputStream = File.OpenWrite(     @"C:\test\source\folder3.bak");

Once you have the normal stream, pass that stream object to the buffered stream's constructor:

BufferedStream bufferedInput =      new BufferedStream(inputStream); BufferedStream bufferedOutput =      new BufferedStream(outputStream);

You can then use the BufferedStream as a normal stream, calling Read() and Write( ) just as you did before. The operating system handles the buffering:

while ( (bytesRead =       bufferedInput.Read(buffer,0,SIZE_BUFF)) > 0 )  {      bufferedOutput.Write(buffer,0,bytesRead);  }

Remember to flush the buffer when you want to ensure that the data is written out to the file:

bufferedOutput.Flush();

This essentially tells the in-memory buffer to flush out its contents.

Note that all streams should be closed, though the finalizer will eventually close them for you if you just let them go out of scope. In a robust program, you should always explicitly close the buffer.


Example 21-5 provides the complete listing.

Example 21-5. Implementing buffered I/O
namespace Programming_CSharp {    using System;    using System.IO;    class Tester    {       const int SizeBuff = 1024;       public static void Main( )       {          // make an instance and run it          Tester t = new Tester( );          t.Run( );       }                // Set it running with a directory name       private void Run( )       {          // create binary streams          Stream inputStream = File.OpenRead(             @"C:\test\source\folder3.cs");          Stream outputStream = File.OpenWrite(             @"C:\test\source\folder3.bak");          // add buffered streams on top of the          // binary streams          BufferedStream bufferedInput =              new BufferedStream(inputStream);          BufferedStream bufferedOutput =              new BufferedStream(outputStream);          byte[] buffer = new Byte[SizeBuff];          int bytesRead;          while ( (bytesRead =              bufferedInput.Read(buffer,0,SizeBuff)) > 0 )          {             bufferedOutput.Write(buffer,0,bytesRead);          }          bufferedOutput.Flush( );          bufferedInput.Close( );          bufferedOutput.Close( );       }    } }

With larger files, this example should run more quickly than Example 21-4 did.

21.2.3. Working with Text Files

If you know that the file you are reading (and writing) contains nothing but text, you might want to use the StreamReader and StreamWriter classes. These classes are designed to make manipulation of text easier. For example, they support the ReadLine( ) and WriteLine( ) methods that read and write a line of text at a time. You've already used WriteLine( ) with the Console object.

To create a StreamReader instance, start by creating a FileInfo object and then call the OpenText() method on that object:

FileInfo theSourceFile =     new FileInfo (@"C:\test\source\test1.cs"); StreamReader stream = theSourceFile.OpenText( );

OpenText( ) returns a StreamReader for the file. With the StreamReader in hand, you can now read the file, line by line:

do {     text = stream.ReadLine(); } while (text != null);

ReadLine( ) reads a line at a time until it reaches the end of the file. The StreamReader will return null at the end of the file.

To create the StreamWriter class, call the StreamWriter constructor, passing in the full name of the file you want to write to:

StreamWriter writer = new StreamWriter(@"C:\test\source\folder3.bak",false);

The second parameter is the Boolean argument append. If the file already exists, true will cause the new data to be appended to the end of the file, and false will cause the file to be overwritten. In this case, pass in false, overwriting the file if it exists.

You can now create a loop to write out the contents of each line of the old file into the new file, and while you're at it, to print the line to the console as well:

do {     text = reader.ReadLine();     writer.WriteLine(text);     Console.WriteLine(text); } while (text != null);

Example 21-6 provides the complete source code.

Example 21-6. Reading and writing to a text file
#region Using directives using System; using System.Collections.Generic; using System.IO; using System.Text; #endregion namespace ReadingWritingToTextFile {    class Tester    {       public static void Main( )       {          // make an instance and run it          Tester t = new Tester( );          t.Run( );       }       // Set it running with a directory name       private void Run( )       {          // open a file          FileInfo theSourceFile = new FileInfo(             @"C:\test\source\test.cs" );          // create a text reader for that file          StreamReader reader = theSourceFile.OpenText( );          // create a text writer to the new file          StreamWriter writer = new StreamWriter(             @"C:\test\source\test.bak", false );          // create a text variable to hold each line          string text;          // walk the file and read every line          // writing both to the console          // and to the file          do          {             text = reader.ReadLine( );             writer.WriteLine( text );             Console.WriteLine( text );          } while ( text != null );          // tidy up          reader.Close( );          writer.Close( );       }    } }

When this program is run, the contents of the original file are written both to the screen and to the new file. Notice the syntax for writing to the console:

Console.WriteLine(text);

This syntax is nearly identical to that used to write to the file:

writer.WriteLine(text);

The key difference is that the WriteLine( ) method of Console is static, while the WriteLine( ) method of StreamWriter, which is inherited from TextWriter, is an instance method, and thus must be called on an object rather than on the class itself.



Programming C#(c) Building. NET Applications with C#
Programming C#: Building .NET Applications with C#
ISBN: 0596006993
EAN: 2147483647
Year: 2003
Pages: 180
Authors: Jesse Liberty

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