October 22, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

File I/O with Streams - Part 1 - An Excerpt from Inside C#, Second Edition

  • March 7, 2003
  • By Tom Archer
  • Send Email »
  • More Articles »

The StreamReader and StreamWriter Classes

As you can see, FileStream is OK for reading and writing raw byte (binary) data. If you want to work with character data, classes such as StreamReader and StreamWriter are more suitable. These classes will use a FileStream object in the background, effectively interposing a character-interpolation layer on top of the raw byte processing. Closing the StreamReader/StreamWriter also closes the underlying FileStream:
public class ReadWriteApp
{
  public static void Main(string[] args)
  {
    FileStream s = new FileStream("Bar.txt", FileMode.Create);
    StreamWriter w = new StreamWriter(s);
    w.Write("Hello World");
    w.Close();

    s = new FileStream("Bar.txt", FileMode.Open);
    StreamReader r = new StreamReader(s);
    string t;
    while ((t = r.ReadLine()) != null)
    {
      Console.WriteLine(t);
    }
    w.Close();
  }
}

The output follows:

Hello World

Recall from Table 1 that the StreamReader and StreamWriter classes can use Encoding to convert characters to bytes and vice versa. To write our data to the file with some encoding other than the default, we need to construct the StreamWriter and StreamReader with an Encoding parameter, as shown here:

//StreamWriter w = new StreamWriter(s);
StreamWriter w = new StreamWriter(s, System.Text.Encoding.BigEndianUnicode);

Please note that encoding is discussed in more detail in Chapter 10, "String Handling and Regular Expressions." of my book.

If you want to open a file that's been set to read-only, you need to pass an additional parameter to the FileStream constructor to specify that you only want to read from the file:

s = new FileStream("../../TextFile1.txt", FileMode.Open, FileAccess.Read);

Paths in C#

Because C# treats the backslash the same way as C and C++ do, if you want to specify a path for a file, you have three choices. You can either use double backslashes, as in:
s = new FileStream("C:\\temp\\Goo.txt", FileMode.Create);
...or use forward (Unix-style) slashes:
s = new FileStream("C:/temp/Goo.txt", FileMode.Create);
...or use the "@" character, which is a control-character suppressor:
s = new FileStream(@"C:\temp\Goo.txt", FileMode.Create);

Memory and Buffered Streams

The classes MemoryStream and BufferedStream are both derived from the abstract Stream class, just as FileStream is. Therefore, MemoryStream and BufferedStream share many of the same characteristics and functionality. Both are designed for streaming data into and out of memory rather than persistent storage. Both can be associated with a stream of another kind.such as a file.if required, and thus both can be used to act as a buffer between memory and persistent storage. The MemoryStream class offers methods such as WriteTo, which will write to another stream. Similarly, a BufferedStream object is normally associated with another stream on construction, and when you close the BufferedStream, its contents are flushed to the associated stream. In the following example, we first create a MemoryStream object with an initial capacity of 64 bytes and print some arbitrary property values. Then we write out 64 bytes and report the same properties. We then use MemoryStream.GetBuffer to get a byte array of the entire contents of the stream and print the values, before finally closing the stream:
class MemStreamApp
{
  static void Main(string[] args)
  {
    MemoryStream m = new MemoryStream(64);
    Console.WriteLine("Length: {0}\tPosition: {1}\tCapacity: {2}",
                      m.Length, m.Position, m.Capacity);

    for (int i = 0; i < 64; i++)
    {
      m.WriteByte((byte)i);
    }
    Console.WriteLine("Length: {0}\tPosition: {1}\tCapacity: {2}",
                      m.Length, m.Position, m.Capacity);

    Console.WriteLine("\nContents:");
    byte[] ba = m.GetBuffer();
    foreach (byte b in ba)
    {
      Console.Write("{0,-3}", b);
    }

    m.Close();
  }
}

Here's the output:

Length: 0       Position: 0     Capacity: 64
Length: 64      Position: 64    Capacity: 64

Contents:
0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
60 61 62 63

Given that the stream is already up to capacity, let's see what happens if we write some more data to it. Will it generate a runtime exception? Will it overrun into unallocated memory? Or will it just dynamically resize the buffer? The last scenario is, of course, the correct one.which you.ll realize if you've examined the previous output closely. Although we set an initial capacity of 64, this wasn.t the initial size of the memory buffer because the buffer was initialized to 0. So, clearly, the buffer is dynamically resized automatically.

string s = "Foo";
for (int i = 0; i < 3; i++)
{
  m.WriteByte((byte)s[i]);
}
Console.WriteLine("\nLength: {0}\tPosition: {1}\tCapacity: {2}",
                  m.Length, 
                  m.Position, 
                  m.Capacity);

Here's the output:

Length: 67      Position: 67    Capacity: 256

As you can see, the minimum block size is 256 bytes.

Finally, let.s associate this MemoryStream with a FileStream before closing it:

FileStream fs = new FileStream("Goo.txt", 
                                FileMode.Create, 
                                FileAccess.Write);
m.WriteTo(fs);
m.Close();

Now let.s do the same thing with a BufferedStream object. Although generally similar, there are some subtle differences between MemoryStream and BufferedStream. First, we can construct a BufferedStream only by initializing it to some other existing Stream.in this example, a file. The buffer in a BufferedStream is managed differently than that in the MemoryStream: if we don.t specify an initial size when we construct the object, it defaults to 4096 bytes. Also, if we've written some data to the BufferedStream and want to read it back, we must use the Read method.because there is no GetBuffer method. Using the Read method means making sure the internal stream pointer is positioned correctly.in this case, at the beginning of the stream.before attempting the read:

class BufStreamApp
{
  static void Main(string[] args)
  {
    // Create a FileStream for the BufferedStream.
    FileStream fs = new FileStream("Hoo.txt",
        FileMode.Create, FileAccess.ReadWrite);
    BufferedStream bs = new BufferedStream(fs);
    Console.WriteLine("Length: {0}\tPosition: {1}",
                      bs.Length, bs.Position);

    for (int i = 0; i < 64; i++)
    {
      bs.WriteByte((byte)i);
    }
    Console.WriteLine("Length: {0}\tPosition: {1}",
                      bs.Length, bs.Position);

    // Reset to the beginning and read the data.
    Console.WriteLine("\nContents:");
    byte[] ba = new byte[bs.Length];
    bs.Position = 0;
    bs.Read(ba, 0, (int)bs.Length);
    foreach (byte b in ba)
    {
      Console.Write("{0,-3}", b);
    }

    // Write some more, exceeding capacity.
    string s = "Foo";
    for (int i = 0; i < 3; i++)
    {
      bs.WriteByte((byte)s[i]);
    }
    Console.WriteLine("\nLength: {0}\tPosition: {1}\t",
                      bs.Length, bs.Position);

    for (int i = 0; i < (256-67)+1; i++)
    {
      bs.WriteByte((byte)i);
    }
    Console.WriteLine("\nLength: {0}\tPosition: {1}\t",
                      bs.Length, bs.Position);

    bs.Close();
  }
}

The console output is listed here:

Length: 0       Position: 0
Length: 64      Position: 64

Contents:
0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
60 61 62 63

Length: 67      Position: 67

Note that the Hoo.txt file will have the same contents as those in the MemoryStream example. We didn.t have to explicitly write from the BufferedStream to the FileStream as we did for MemoryStream because the BufferedStream must be associated with another stream in the first place. All writes to the BufferedStream will be buffered for writing to the associated stream, and merely closing the BufferedStream object is enough to flush any pending writes to the file.



Page 3 of 6



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel