Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Visual Basic 2005 Express Edition - From Novice To Professional (2006)

.pdf
Скачиваний:
387
Добавлен:
17.08.2013
Размер:
21.25 Mб
Скачать

370 C H A P T E R 1 4 F I L E S A N D S T R E A M S

Run the program now and you’ll see the output shown in Figure 14-5.

Figure 14-5. The program successfully reads and writes data to a file.

You’ll also find a brand new file on your hard disk called C:\test.txt. You can open this in Notepad to see the contents.

Working with Streams

I mentioned streams back at the start of this chapter. You can think of all the data inside a file as a stream that you can both read from and write to. But we just covered how to read and write with files when we covered the File object. In fact, using

System.IO.File.ReadAllLines() and System.IO.File.WriteAllLines() is so simple and painless to use, you may well be wondering why on earth you should even care about streams at all.

The ReadAll...() methods (ReadAllLines, ReadAllBytes, ReadAllText) are indeed simple to use, but they are “all or nothing” solutions. They don’t provide you a great deal of granularity in terms of working with the data in a file; you can either read everything, or nothing at all. If you drop down to working with streams, though, you have a lot more control. Better yet, you aren’t consigned to just working with files. Streams can represent data

C H A P T E R 1 4 F I L E S A N D S T R E A M S

371

from almost any source. You can use streams to read data from a remote server on the Internet, from a string variable, from a block of memory, and of course from a file. In fact, as you dive deeper into the .NET Framework in your own projects, you’ll come across plenty of times when knowing how to work with a data stream is valuable.

Just like the File and Directory classes, streams live in the System.IO namespace. This of course means that you need to add a Using System.IO statement to the top of any class file you write that needs to work with them. If you search within the online help for stream hierarchy, you’ll see the complete list of all the stream types in the .NET Framework, some even covering weird and wonderful data sources such as DirectX graphics streams (one for the games programmers out there), Oracle database streams, and CryptoStreams for working with encrypted data. We’re going to focus here on the two most common types of stream you’ll use: FileStream and NetworkStream.

The Core Concepts

All streams derive from a base abstract class called System.IO.Stream. This of course means that after you know how to use one type of stream, you know how to use them all. An abstract class does, after all, just force derived classes to implement the same interface.

Similarly, the concepts for actually working with a stream apply to all streams. Although you can use methods in the Stream class for reading and writing data, the best way to work with them is to use a Reader or Writer class.

If a stream is the actual data that you want, a reader provides you with a nice easy-to- use interface to get at that data. Think of a stream as a fire hose, and a reader as a handy, controllable nozzle that you can use to get at the information without drowning in a sea of data. Conversely (and this is where the analogy falls down somewhat), a writer lets you put data back in easily.

The work flow for using a stream is simple:

1.Identify a resource containing data (that is, the file you want to work with).

2.Connect a stream to it.

3.Drop a reader or writer on top of the stream.

4.Work with the data.

Let’s start putting this into real terms.

372

C H A P T E R 1 4 F I L E S A N D S T R E A M S

Working with File Streams

Let’s say you have a file containing data that you want to read. The code for getting at that data looks like this:

Sub Main()

Dim stream As New FileStream("c:\test.txt", FileMode.Open)

Dim reader As New StreamReader(stream)

Dim line As String = reader.ReadLine()

Do

Console.WriteLine(line) line = reader.ReadLine()

Loop While Not line Is Nothing reader.Close() Console.ReadLine()

End Sub

Of course you’ll also need to add Imports System.IO to the top of the source code, and this code does also assume that the file in question exists.

The first line of code opens the stream. Notice that when you use a file stream, you can specify not only the name, but also how you want to work with the file. In this case I’m opening the file for reading, by passing in the FileMode.Open value. There are a number of different values in the FileMode enumeration, and they are listed in Table 14-2.

Table 14-2. The FileMode Enumeration Values

FileMode Value

Description

FileMode.Append

Opens an existing file for writing. If the file doesn’t exist, a new one is

 

created. The stream is expecting to have data added to the end of the

 

file in either case.

FileMode.Create

Creates a new file, again for writing. If the file exists, this will wipe out

 

any data it contains, so use with caution.

FileMode.CreateNew

This tells the stream that we want to create a new file and we don’t

 

expect the file to already exist. If it does and we open its stream with

 

this FileMode, an exception is thrown.

FileMode.Open

Simply opens an existing file, for either reading or writing. If the file

 

does not exist, an exception is thrown.

C H A P T E R 1 4 F I L E S A N D S T R E A M S

373

FileMode Value

Description

FileMode.OpenOrCreate

Opens an existing file just like Open does. If the file doesn’t exist, it gets

 

created. Unlike FileMode.Append, though, FileMode.OpenOrCreate can

 

be used to open a file for both reading and writing.

FileMode.Truncate

This is a strange one. It expects the file to already exist, and if it

 

doesn’t, an exception is thrown. If the file does exist, all the data it

 

contains is removed, ready for us to start writing to the file as if it were

 

a brand new one.

 

 

Moving on—once the stream has been opened, a StreamReader is created. The stream gets passed into the StreamReader’s constructor in order to attach the Reader to the stream.

Finally, a loop is created to read from the file. StreamReader has a number of methods to read data from a file, ReadLine() being one of them. ReadLine() will read a line of text from a file and return it as a string. If there is no more data to be read in the file, it will return Nothing, hence the check that we have at the end of our While loop. When we are done reading, the reader is closed with a call to reader.Close(). This frees up the resources inside the computer that the stream was taking up and—more important—sev- ers the connection to the stream itself. You should always do this.

If you like, key this code into a console project (don’t forget to include the Imports System.IO line at the top of the source file) and run it. Providing you point it at an existing file, you’ll see the file contents written out to the console.

So, what’s the big advantage with this method over simply typing File.ReadAllLines()? The biggest advantage is that loop. Let’s say that you wanted to read a file that consisted of a few gigabytes of data in order to find something specific. With File.ReadAllLines(), you’d have to sit and wait while the method slowly chugged through the entire file, filling a huge string array in your program in the process. Only when the read was complete would you be able to search the array to see whether the piece of data you were interested in was actually in the file.

With the Stream and StreamReader approach, that doesn’t happen. First, in our example code here you’ll always be working with only a single line of data so you don’t run the risk of your computer’s memory filling up. Second, it would be easy to provide visual feedback to the user (perhaps with a progress bar or animated icon) to set the user’s mind at ease that the program hasn’t crashed. Third, because you read the file a line at a time, you can at any point stop the read. Perhaps the data you are looking for occurs early on in the file, or perhaps the user hits a Cancel button. In either case you are free to stop the read and let the program, and its user, get on with other more pressing tasks.

374

C H A P T E R 1 4 F I L E S A N D S T R E A M S

You may also be thinking that that’s an awful lot of code for something that the File class will let you do in just one line of code. You’re right. It is. As always with Visual Basic and the .NET Framework, though, we can simplify it quite a lot. Both the StreamReader and StreamWriter classes have constructors that can take a filename as a parameter. When used in this way, the StreamReader and StreamWriter objects will handle all the mundane work of actually opening the underlying file for you. So, the preceding example could be simplified by removing the Stream variable altogether and opening the file with a simple call like this:

Dim reader As New StreamReader("C:\myfile.txt")

Admittedly, this doesn’t slim down the code very much, but it does reduce some of the complexity.

The final, and perhaps most important reason to use streams, readers, and writers is for dealing with nontext. What if the file you wanted to read didn’t contain nicely formatted text with a handy carriage return character to segment the lines in the file? StreamReader provides three methods for just that situation: Read(), ReadBlock(), and

ReadToEnd().

StreamReader.Read() reads just a single byte from the file and returns it as an Integer value. It’s important to note that this Integer is the value of the byte. If you had a file of numbers (perhaps your lottery syndicate’s numbers), calling Read() would not return each in turn. What you’ll get instead are the numbers that represent each and every digit of the lottery numbers. When the end of the stream is reached, StreamReader.Read() returns -1.

The Read() method can even read a number of bytes from a file in one go. Take a look at this:

Dim dataBlock(9) As Char reader.Read(dataBlock, 0, 5)

This creates a 10-character array and then has the reader pull 5 bytes from a stream and put them into dataBlock[0] through dataBlock[4]. The second parameter to the call tells the reader which element of the array to overwrite, and the third parameter tells the reader just how many bytes to get.

This overload actually works exactly the same way as ReadBlock() works. For example:

Dim dataBlock(9) As New Char reader.ReadBlock(dataBlock, 0, 5)

Both Read() and ReadBlock() return a number telling you just how many characters were read from the file. If it’s less than the number you asked for, your code can assume that the end of the file has been reached.

Writing data to a file with a StreamWriter is almost as simple as reading. There are two methods on the StreamWriter class (Write() and WriteLine()) that you can use to send data to a stream. Write() is an overloaded method that you can send literally any kind of

C H A P T E R 1 4 F I L E S A N D S T R E A M S

375

data to (String, Char, Byte, Integer, and so on) one data item at a time. WriteLine() is the same, but it puts a line break at the end of each item.

One thing you do need to bear in mind, though, is how you finish writing. There is a risk that data you have asked to write has not been written to the file yet. Instead it could be sitting in a file buffer within Windows. So, when you are finished writing data, you must call a method named Flush() on the writer to empty that buffer, and then close the file with a call to Close().

Let’s take a look at a brief “Try It Out” to demonstrate how to work with a StreamWriter.

Try It Out: Using Write and WriteLine

Start up a new console project in Visual Basic 2005 Express. When the code editor appears, add the Imports System.IO statement to the top of the source file. Your code will look like mine in Figure 14-6.

Figure 14-6. Adding System.IO to the list of namespaces to use

The first thing you’re going to do in this program is write a new file to the hard disk. Add the highlighted code to the Main() method:

Sub Main()

Dim writer As New StreamWriter("c:\myfile.txt") For i As Integer = 1 To 10

writer.WriteLine(i.ToString())

Next writer.Flush() writer.Close()

End Sub

The first thing you do here is create a writer and use its constructor to connect it to a stream for writing a new file. With that out of the way, the code jumps into a simple loop to count from 1 to 10.

376

C H A P T E R 1 4 F I L E S A N D S T R E A M S

Within the loop you just turn the loop index (i) into a string and pass it across to the writer’s WriteLine() method.

Finally, when the loop is finished, the code flushes the writer and closes it down. The net result of all this of course is that you have a new file on the hard disk called C:\myfile.txt that holds numbers from 1 to 10. Let’s prove that by adding in some more code to quickly read the file and write it out to the console:

Sub Main()

Dim writer As New StreamWriter("c:\myfile.txt") For i As Integer = 1 To 10

writer.WriteLine(i.ToString())

Next writer.Flush() writer.Close()

For Each line As String In File.ReadAllLines("c:\myfile.txt")

Console.WriteLine(line)

Next

Console.ReadLine()

End Sub

Here you just use the File.ReadAllLines() method to quickly grab an array of strings from the file in order to iterate over them and write them out to the console.

Run the program now and you’ll see a console like mine in Figure 14-7.

Figure 14-7. The program writes a file and then reads it back to dump it out to the console, like this.

When the program has finished, you’ll need to tap your Enter key to close down the console thanks to our Console.ReadLine() method call at the end of the program.

C H A P T E R 1 4 F I L E S A N D S T R E A M S

377

So far the program did everything you’d expect it to. Let’s change the WriteLine() call to Write() and see what happens. Edit the code as highlighted here:

Sub Main()

Dim writer As New StreamWriter("c:\myfile.txt") For i As Integer = 1 To 10

writer.Write(i.ToString())

Next writer.Flush() writer.Close()

For Each line As String In File.ReadAllLines("c:\myfile.txt")

Console.WriteLine(line)

Next

Console.ReadLine()

End Sub

If you run the program now, you’ll see the output is quite different. You can see mine in Figure 14-8.

Figure 14-8. Changing the program to just call Write() instead of WriteLine() dramatically

changes the format of our file.

378

C H A P T E R 1 4 F I L E S A N D S T R E A M S

WriteLine() puts a line break after every piece of data you send to the file, effectively creating multiple lines of text in the file. Write(), on the other hand, just dumps everything straight to the file, warts and all.

Working with Network Streams

Now you know how to work with streams, stream readers, and stream writers, you are well equipped to work with practically any kind of stream. Let’s put that theory to the test and take a look at network streams.

The basic network stream class is not found in the System.IO namespace. NetworkStream lives in System.Net.Sockets, a scary namespace full of low-level networking classes with strange names like Socket! We’ll stay well away from there, because we can get at a network stream very easily via the System.Net.WebRequest class. Just as with the low-level file stuff I mentioned just now, if you really want to use sockets to directly connect to some obscure protocol on a remote server, the chances are you know what you want to do and how to do it, so I won’t get in the way. The vast majority of us, though, feel warm and safe with the handy WebRequest class.

WebRequest has two parts to it—a request and a response. When you point the web browser of your choice at a remote website, the web browser first sends data to the server to tell it exactly what you want to see in the browser. The server processes the request and then gives you a response. We can get a stream from this response object. Let’s dive straight into a “Try it Out” to see this in action.

WORKING DIRECTLY WITH STREAMS

You don’t have to have a reader to work with a stream of data—you can sip straight from the fire hose. System.IO.Stream provides a method for reading into an array of bytes (Read()), which works the same as the reader’s Read() method. You can also use ReadByte() to grab a single byte from the file. In addition to all that, you can jump forward and backward in a file by using the Seek() method. You simply pass in the number of bytes to move forward or backward, and specify where you want to move from (usually

SeekMode.Current).

Although this is a powerful feature, the fact of the matter is that only a handful of you reading this will ever want to do that. It’s great if you’re writing code to deal with streams from strange hardware devices or binary files that you have documentation for, telling you exactly which bytes in a file do what. For that reason I’m not going to cover it here. If this is something that you need to do, take a look at the online help for

Stream.Read and Stream.Seek for more information.

C H A P T E R 1 4 F I L E S A N D S T R E A M S

379

Try It Out: Working with Network Streams

Create a new console project and add two Imports statements to the top of the Module1.vb file so that you can work with both streams and WebRequests:

Imports System.Net

Imports System.IO

Module Module1

Sub Main()

End Sub

End Module

Now, let’s work through the WebRequest way of doing things step by step.

The first thing that you’ll need to do is create a WebRequest object. This is done not by just instantiating a new WebRequest object, but instead by calling a shared method named Create():

Sub Main()

Dim request As WebRequest

request = WebRequest.Create("http://www.apress.com")

End Sub

This creates the object and makes the request for a web page from a remote web server. The next step is to grab the response from the server. To do this, you need a WebResponse object:

Sub Main()

Dim request As WebRequest

request = WebRequest.Create("http://www.apress.com")

Dim response As WebResponse = request.GetResponse()

End Sub