You can use different Stream classes to read and write files, including Stream classes for text files and binary files, and specialized classes to allow you to compress data or store data in memory. You can also use streams to store files in isolated storage, a private file system managed by the .NET Framework.
At the end of this tutorial, you will be able to:
- Read and write text files, binary files, and strings
- Write to a stream stored in memory
- Compress a file when writing it
- Use isolated storage to improve the privacy of data you store
The .NET Framework includes classes that make it easy to read and write text files. For example, the TextWriter and TextReader classes are stream classes that can be used only with text files.
To write a text file, follow these steps:
Create a new instance of TextWriter. TextWriter does not have its own constructor, but you can use the StringWriter constructor, or call File.CreateText().
Call TextWriter.Write and TextWriter.WriteLine to save text to the file.
Call TextWriter.Close to close the file.
The following code sample demonstrates this:
Dim tw As TextWriter = File.CreateText("output.txt" tw.WriteLine("Hello, world!") tw.Close()
Reading Text Files
The quickest way to read an entire text file is to create an instance of the TextReader class and then call the TextReader.ReadToEnd method. As with most file operations, be sure to call TextReader.Close afterwards.
The following code sample demonstrates how to use TextReader:
Dim tr As TextReader = File.OpenText("C:\file.txt") Console.Write(tr.ReadToEnd())tr.Close()
You can save non-text data to a file using two different techniques: binary streams and serialization. Serialization, discussed in Module 5, is typically the more efficient choice. However, you should also be familiar with binary streams.
Use the BinaryReader and BinaryWriter classes in much the same way you use TextReader and TextWriter. BinaryReader and BinaryWriter are different in a couple of important ways:
When you create either class, you must provide an instance of the FileStream class to specify which file to read or write.
You can write non-text types, including character array, byte arrays, a variety of number types, and Boolean values.
When reading from the binary file, you must use a method specific the type that you are reading. For example, if you write a 32-bit integer, you must read a 32-bit integer using the BinaryReader.ReadInt32 method. Similarly, you must use the BinaryReader.ReadBoolean method to read a Boolean value.
' Create a file Dim fs As New FileStream("data.bin", FileMode.Create) Dim w As New BinaryWriter(fs) ' Write data to the file w.Write(32.15R) ' Write an double w.Write(16) ' Write an Int32 w.Write(False) ' Write a Boolean value w.Write("Hello, world!") ' Write a string ' Close the file w.Close() fs.Close() ' Open the file for reading fs = New FileStream("data.bin", FileMode.Open, FileAccess.Read) Dim r As New BinaryReader(fs) ' Read the data from the file Console.WriteLine(r.ReadDouble()) Console.WriteLine(r.ReadInt32()) Console.WriteLine(r.ReadBoolean()) Console.WriteLine(r.ReadString()) ' Close the file r.Close() fs.Close()
Using Streams (Presentation)
Watch my presentation about using streams and storing files with the .NET Framework:
Here’s the full transcript:
The .NET Framework uses streams to send data to a variety of different medias. For example, the most common use is to send data to a file on the file system. You can do this using several different Stream variants, such as TextWriter or BinaryWriter. The most basic of those is the StreamWriter class. Using StreamWriter, you can specify a filename in the constructor, and then use the StreamWriter.Write method to save data to that file. If you need to read data from that file later, you would use the StreamReader class.
You can also layer other classes on top of StreamWriter. If you want to write a compressed file, you would create an instance of the GZipStream class. Then, when you create your StreamWriter class, you provide that GZipStream instance as part of the StreamWriter constructor. Then, anytime you call StreamWriter.Write, that data is funneled through the GZipStream instance. GZipStream compresses the data using a type of compression called GZip compression. When you need to read the data back later, you can’t just use an ordinary StreamWriter, or the data will come back as gibberish. So, you need to do exactly the same thing in reverse—you create a GZipStream instance set to decompress the data, and then create a StreamReader instance using your GZipStream instance. Then, you’ll be able to read directly from the compressed file.
The .NET Framework actually provides two different compression methods for writing compressed files. The one most people use is GZip, that I just discussed. It’s a more effective compression method than the Deflate compression method. They’re just different compression methods; they accomplish the same thing. GZip is more common; it’s even built into the file system. Deflate is a little less common and a little less efficient. So, either one will compress a file, but the compressed file will probably be a little smaller if you use GZip.
Now, the beauty of using streams is you can use a variety of different media, not just the file system. In the .NET Framework, you probably won’t write to the file system unless you need to access the file in a different application. Instead, you should use Isolated Storage. Isolated Storage is stored on the file system, so it is permanent, and you can close your application and re-launch your application and the data will still be there. Isolated storage is, in fact, a file on the file system. But, the .NET Framework treats it as its own entity. So, an application can write to Isolated Storage and read it back almost as if it were part of the file system, but it has some benefits that writing directly to the file system does not provide. First, Isolated Storage is protected by the .NET Framework. Different users cannot access Isolated Storage. Different assemblies cannot access another assembly’s isolated storage. Perhaps most importantly, Isolated Storage works with the security principal of least privilege because it takes fewer .NET Framework privileges to Isolated Storage than it does to write directly to the file system.. So, if you are writing a file that will only be read back by your assembly and from the same user, you should always write to Isolated Storage instead of the file system.
When you use Isolated Storage you can protect it in two different ways. You can specify the user domain, which means only that assembly run by that user can access files in Isolated Storage. Or, you can specify protection for the application domain. If you specify application domain, only other assemblies running in the same application domain can access the isolated storage. Application domains really only become important when you’re writing multi-threaded applications or applications that spawn other assemblies. So, at this point, you probably won’t use it. But, we will discuss application domains in later modules.
Another way to use StreamWriter is to write through the MemoryStream class. Just as we discussed when using GZipStream or DeflateStream, you will create an instance of MemoryStream, and then specify the instance when you create your StreamWriter instance. Then, when you call StreamWriter.Write to save data, it will be processed by the MemoryStream class. MemoryStream completely bypasses the file system and stores the data in the computer’s RAM, in the memory itself. So, it’s not persistent. If you save data to a MemoryStream instance and the user closes the application, the data’s gone. If the computer shuts down, the data’s gone. It’s stored in the computer’s memory, so it’s only persistent while the application and the computer are running. For that reason, you wouldn’t normally use MemoryStream unless you need to prepare a file to be written to the file system later. For example, if you need to gradually create a file over the course of five minutes—perhaps you’re querying a database server and processing data and then outputting it to a file, you might write that data to a MemoryStream, and then once the data had been processed, you might copy the data to the file system using another instance of StreamWriter. That might be useful when multiple users might be accessing the same file, or when you need to minimize the number of writes that you make to a particular file. So, think of a MemoryStream as a way to pre-write data to a file. But, remember that it’s not backed by the file system, so if your application is interrupted, it’s gone forever.
Reading and Writing Binary Files (Video Demonstration)
Watch me demonstrate using binary files. Be sure to watch it in HD and full-screen. You can download the source code here: Read and Write Binary Files in Visual Basic.NET.
Here’s the full transcript:
To demonstrate how to read and write binary files, I’ve created a Visual Basic console application. I’m going to create two subroutines-the first is going to write the current time to a binary file, and the second is going to read it in from the console. First, I’ll create the structure for them.
Now, I’m going to need to create a FileStream object in order to create the BinaryWriter instance, and doing that requires the System.IO namespace that I’m adding now. Now, I’ll create the FileStream object by specifying the filename and the parameter that specifying we’re going to create the file. Sorry, I typed using instead of imports.
Now, I’m going to create a BinaryWriter object by specifying the FileStream I just created as part of the constructor. That provides the filename to the BinaryWriter.
Now, I’m ready to actually write the DateTime to the binary file using the BinaryWriter.Write method. Notice, of all of the different types that I can give to the Write method, none of these support the DateTime parameter. All I can do is to write the house and minutes separately, and then read them back separately. Those parameters are each integers.
Now that I’ve written the data I am going to close the BinaryWriter and the FileStream objects.
Now I’m ready to create the ShowPreviousTime subroutine. I need to create a FileStream object exactly as I did when creating the BinaryWriter. The BinaryReader class takes basically the same parameters, a FileStream object, except this time I’m going to specify FileMode.Open because I’m not going to be creating a file–I’m only trying to read the file.
Now I’m going to create the BinaryReader and specify the FileStream object for it.
Now I’m going to read in the objects in exactly the same order as I wrote them. I’m going to use exactly the same types, too. So, I’m going to create a string and use that to concatenate the time in a readable format. I’m reading them in as Int32s and adding a colon in between them.
Now that I’ve concatenated those into a readable time, I’m going to send it to the console with Console.WriteLine.
One last thing to do—call those subroutines from the Main method. Then, I’ll call Console.ReadKey to make the console wait for me so we can see the display. Now, I’ll run it.
So, you can probably see my mistake there—I’m too accustomed to using C#. I need to use ampersands here instead of plus symbols.
And, there we go—the time is 5:44.
Using Compressed Files (Video Demonstration)
Now, watch this video demonstrating how to use compressed files in Visual Basic. You can download the source code here: Compressing Files in Visual Basic.
Here’s the full transcript:
In this project I’m going to use StreamWriter and StreamReader to write data and read it from a file, and then I’m going to do the same thing using the GZipStream class to compress the data. We’ll compare the file sizes.
We know we’re going to need the System.IO namespace, because that’s required by StreamWriter and StreamReader. First, I’ll create the StreamWriter instance and specify the filename—I’ll call it data.bin. Now, writing to this file is really easy—we just need to call StreamWriter.Write.
I’m going to make the file fairly big, so I’m going to create a For loop to write “hello, world” 10,000 times to it. Notice that StreamWriter.Write can take a variety of different types. I’m just writing a string to it, but you can also write integers, or Boolean values, or character arrays.
We open it up, so we need to close it at the end. The file has been created at this point, and we just need to create a StreamReader to read it in. Creating the StreamReader is done in exactly the same way as creating the StreamWriter was—we just need to specify the filename.
Now, I’m going to display the entire file to the console by calling StreamReader.ReadToEnd. In practice, you probably wouldn’t read the entire file in this way—you’d probably write some binary data to it and then read it back selectively, as you would a binary file.
One last thing I want to do, just for fun, is to check the file size so we can see how effective the compression is when we’re doing. I’ll do this by creating a FileInfo instance and specifying the filename. Now, I’ll write to the console the size of the file. Length is just an integer that is the size of the file in bytes.
One last thing, we’ll do a Console.ReadKey to pause the console at the end.
The total size of the file is about 140k.
Now I’m going to go back and update this to use the GZipStream class instead of using StreamWriter to write directly to the file. I need to Import the System.IO.Compression namespace to get GZipStream.
I need to, for the first GZipStream parameter, provide a stream. One easy way to do that is to call File.Create. As the second parameter, I need to specify either CompressionMode.Compress or Decompress. I’m writing to the file, so I want to compress it to make it smaller. When I read it, I will specify Decompress. When I create the StreamWriter, this is an overloaded constructor, so instead of specifying the filename I will specify my instance of GZipStream.
Ultimately it writes to the same file, but it goes through that extra compression layer provided by the GZipStream class. I don’t need to change the writing at all, I just need to close the GZipStream instance.
When I read it, I need to do exactly the same thing—in fact, I’ll just copy the GZipStream constructor. Instead of calling File.Create, I’ll call File.OpenRead. The filename is, of course, the same. Instead of compressing it, I’ll decompress it.
Once again, the reading code does not need to change, I just need to close my instance of GZipStream.
Whoops, I almost forgot to replace my StreamReader constructor to use my new instance of GZipStream. So, this is ready to run. This time, the file size is 1.7k about—we’re down to about 1% of the original file size
In this case, the compression was extremely effective, but it’s not really a real-world scenario. In the real world, you won’t simply be repeating the same phrase over and over again, which would make the compression extremely effective. In the real world, you will see a high-level of compression for text. Probably not 99%, but maybe 90%. For a binary file, you might see 30%-60% compression. If you were compressing a JPEG or MP3 which are already compressed, you won’t see any compression. In fact, the compression might actually make the file size a little bit bigger.
Return to the .NET Framework Fundamentals Tutorials Table of Contents.