[ Team LiB ] |
21.2 Reading and Writing DataReading and writing data is accomplished with the Stream class. Remember streams? This is a chapter about streams.[1]
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, which provides buffered I/O and which can be used in conjunction with any of the other stream classes. The principal classes involved with I/O are summarized in Table 21-5.
21.2.1 Binary FilesThis 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 buffer-ful 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 filenamespace 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( ) { // 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( ); } } } 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. 21.2.2 Buffered StreamsIn the previous example, you created a buffer to read into. When you called Read( ), a buffer-ful 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 allows the operating system to create its own internal buffer, and read bytes to and from the backing store in whatever increments it thinks is 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); } The only change is that you must 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 operating system to take the entire contents of the in-memory buffer and write it out to disk. Example 21-5 provides the complete listing. Example 21-5. Implementing buffered I/Onamespace 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 FilesIf 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 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 filenamespace Programming_CSharp { using System; using System.IO; 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. |
[ Team LiB ] |