Gigi Labs

Please follow Gigi Labs for the latest articles.

Friday, May 10, 2013

C#: Working with Streams

Hello everyone and welcome to this fresh article at Programmer's Ranch! :)

Today we will be talking about streams. I won't tell you what they are just yet. Instead, I'll start by telling you why we need them.

In my article C#: Creating a grep-like tool using Files, Strings and Loops, I showed how you can load a whole file into a variable (i.e. into memory). This is great for small files. However, nowadays is it not very rare for files to be several gigabytes in size. Clearly, that's a bit too much to load into memory, especially when you don't need to process the entire file at once.

Streams give us a way to process files (and other types of I/O, which we'll see later) bit by bit. In order to understand this better, let's use an analogy. In nature, a stream is like a small river. So let's imagine you have this setting:


Imagine the water is flowing from the top-right to the bottom-left. At any given moment, there is only a certain amount of water passing underneath the bridge, even though the river in itself is very long. An even better scenario is a dam. Water from a river flows through a dam, a bit at a time, and turns the turbines.

Let's see how this translates to programming in an example. Start a new SharpDevelop project, and add the chat.txt file following the instructions in C#: Creating a grep-like tool using Files, Strings and Loops. Remember to set Copy To Output to Always!

As in that article, add the following near the top of the code file:

using System.IO;

Instead of the Hello World automatically generated by SharpDevelop, put this:

             FileStream fileStream = File.OpenRead("chat.txt");

File.OpenRead() gives you a FileStream object. When working with stream objects, it is usually convenient to read them using a StreamReader class:

             StreamReader reader = new StreamReader(fileStream);

The StreamReader works on a particular stream, so we pass the file stream as a parameter. This is called wrapping the stream in a StreamReader. We can now use this StreamReader to read the file line by line:

            String line = "";
            while (line != null)
            {
                line = reader.ReadLine();
                Console.WriteLine(line);
            }

This has the effect of showing the contents of a file, just like the type command in the Windows command line, or the cat command in a Linux shell. The good thing about this is that only one line of text is loaded into memory at any given time, so this will work even for very large files. The while loop ensures that all lines are read until the end of the file; when there are no more lines to read, StreamReader.ReadLine() returns null, and the loop ends. null is a special value that we'll see more of when we discuss classes.

The final thing that is very important here is to remember to close the file at the end:

            fileStream.Close();

Always remember to do this, or it will bite you later. The best way to deal with things that need to be closed (or disposed) is to work with the using statement, as shown below (full code). Note: this is not the same as the using statement used to work with System.IO, as above!

            using (FileStream fileStream = File.OpenRead("chat.txt"))
            {
                StreamReader reader = new StreamReader(fileStream);
              
                String line = "";
                while (line != null)
                {
                    line = reader.ReadLine();
                    Console.WriteLine(line);
                }
            }
          
            Console.Write("Press any key to continue . . . ");
            Console.ReadKey(true);

The using statement is a great way of working with resources that need to be disposed, such as files. The resource declared within the brackets (in this case fileStream) is automatically closed when the body of the using statement ends (i.e. at the closing brace, '}').

Great! Today we have learned that streams are simple to work with, and they allow us to work bit by bit on very large files. We also learned about the using statement, which is a best practice when working with disposable resources.

As an exercise, use a StreamWriter to write those lines that contain the word "pastizzi" to a text file called "output.txt".

Streams are very useful and we'll see more of them in the future, especially when we talk about network programming. Keep following this blog, as fun things are coming your way! :)

2 comments:

Note: Only a member of this blog may post a comment.