Since we've been talking for a few weeks now about the use of streams by way of discussing Java's network facilities, it's now time to go back and give some serious attention to the underpinnings of the I/O system created to support Java. I/O System Support for Java
If you follow the definition given in the Java tutorial, "A stream is a flowing sequence of characters." A true, if somewhat decorative statement, this is more or less what we're onto here. For those of you who have written in other modern (and even not so modern) programming languages, these will probably correspond (in function, at least) to the I/O streams you're already used to using.
Practically speaking, this definition of a stream, and the model of high-level language routines that evolved around it, begins with something commonly known in the CS world as sequential file processing. This is a simplistic model for dealing with files stored on a disk. One can imagine that back in the bad old days of computing (which this author glumly admits to having been born too late for), this was a common method for handling some file access. You could only read from or write to your files in sequential order (beginning to end). Yes, that means that to change something in the middle of a file required reading the entire thing, changing the copy in memory, and writing it all back to disk in one fell swoop.
Naturally, this is untenable, and we don't settle for it. But the simplicity of this model is attractive, and remnants of it are still with us today. Use of a "stream metaphor" is still en vogue among high-level language designers, as opposed to, for instance, a model that simulates the presence of an entire hypothetical file's contents in memory. We certainly don't want to give the impression that this system has stuck around for purely sentimental reasons. The balance that's been struck with it has proven to be quite effective for most applications, and in fact there's a class in the I/O library that allows for handling strings in memory using a stream (called
StringBufferInputStream).Let's qualify what we mean when we talk about striking balances. Today what we have in Java is a loose model that allows for a variety of features in addition to this draconian simplicity. If we take a look in
java.io, we find two base classes for handling input and output by streams, aptly namedInputStreamandOutputStream. On their own, certainly they're not much. Naturally, however, a number of additional utility classes are provided in the standard API, which are instituted out of (or on top of, however you like to look at it) these base classes that provide a large body of additional features, which we will get into shortly.The base output class is so simple it's vaguely comic. If you look at its description in the API, it contains only two real working methods besides its constructor:
writeandclose. The write method simply transmits (euphemistically speaking) the byte or bytes passed as an argument over the stream;closeshuts the whole affair down. Keep in mind that not only can you do this yourself, but it is also done implicitly when your stream is garbage collected.The base input class has a little more. Aside from the obvious
readandclosemethods, it gives us askipmethod for jumping an arbitrary number of bytes into whatever we're reading.Both of these classes also have stub methods that do nothing but serve as placeholders for classes that will inherit from them. For instance, the InputStream class has a method called
available()that does the wonderful service of informing how many more bytes we may read before blocking occurs. Blocking, for those who've not yet had the pleasure of working extensively with I/O operations, is the condition that occurs when a program is reading from a stream that suddenly has no more data to read, but has still not yet ended.For instance, you may be reading the I/O stream that corresponds to keyboard input. Strictly speaking, this is a stream that will never exactly end, in the same sense that a file will. But in a situation where nothing is being typed and you are in the middle of a
read, "blocking" occurs. This translates into your program (or more specifically, your thread) being frozen until new data become available on the stream to satisfy your read request.Thus, the
availablemethod tells us exactly how much we can expect to get before we're likely to be stuck, so to speak. This actually has slightly different meanings in different contexts, of course. As we'll soon see, this feature can be indispensable.Another feature we find stubbed out in
InputStreamare themark,reset, andmarkSupportedmethods. Briefly, these allow you to "mark" your place in a stream and then "reset" back to it. ThemarkSupportedreturns true or false depending on whether this feature is supported in whatever subclass of the stream you happen to be using. (Naturally, in the base class, it always returns false.) Of course, this feature is rather primitive, and, where feasible, a stream subclass can provide you with the ability to skip about in the position of your stream in a far more nimble fashion.Next week, we'll talk about practical uses and interesting features of the I/O library. In the mean time, check out the Java tutorial on streams. Stay tuned.