Streams

To save data permanently within a Java program, or to retrieve that data later, you must use at least one stream. A stream is an object that takes information from one source and sends it somewhere else. The name is inspired by water streams that take fish, boats, inner tube riders, and industrial pollutants from one place to another. Streams connect a diverse variety of sources, including computer programs, hard drives, Internet servers, computer memory, and CD-ROMs. Because all these things use streams, once you learn how to work with one kind of data, you will be able to work with others in the same manner. During this hour, you will use streams to read and write data stored in files on your computer. There are two kinds of streams:

  • Input streams, which read data from a source
  • Output streams, which write data to a source

All input and output streams are made up of bytes, individual integers with values ranging from 0 to 255. You can use this format to represent data, such as executable programs, word-processing documents, and MP3 music files, but those are only a small sampling of what bytes can represent. A byte stream is used to read and write this kind of data.

By the Way

Java class files are stored as bytes in a form called bytecode. The Java interpreter runs bytecode, which doesn't actually have to be produced by the Java language. It can run compiled bytecode produced by other languages, including NetRexx and JPython. You also will hear the Java interpreter referred to as the "bytecode interpreter."


A more specialized way to work with data is in the form of characters—individual letters, numbers, punctuation, and the like. A character stream can be used when you are reading and writing a text source. Whether you work with a stream of bytes, characters, or other kinds of information, the overall process is the same:

  • Create a stream object associated with the data.
  • Call methods of the stream to either put information in the stream or take information out of it.
  • Close the stream by calling the object's close() method.

Files

In Java, files are represented by the File class, which also is part of the java.io package. Files can be read from hard drives, floppy drives, CD-ROMs, and other storage devices. A File object can represent files that already exist or files you want to create. To create a File object, use the name of the file as the constructor, as in this example:

File tutorialName = new File("address.dat");


This creates an object for a file named address.dat in the current folder. You also can include a path in the filename:

File tutorialName = new File("data\address.dat");


By the Way

This example works on a Windows system, which uses the backslash (\) character as a separator in path and filenames. Linux and other Unix-based systems use a forward slash (/) character instead. To write a Java program that refers to files in a way that works regardless of the operating system, use the class variable File.pathSeparator instead of a forward or backslash, as in this statement:

File tutorialName = new File("data" + File.pathSeparator
 + "address.dat");



Once you have a File object, you can call several useful methods on that object:

  • exists()true if the file exists, false otherwise
  • getName()— The name of the file, as a String
  • length()— The size of the file, as a long value
  • createNewFile()— Creates a file of the same name, if one does not exist already
  • delete()— Deletes the file, if it exists
  • renameTo(File)— Renames the file, using the name of the File object specified as an argument

You also can use a File object to represent a folder on your system rather than a file. Specify the folder name in the File constructor, which can be absolute (such as "C:\MyDocuments\") or relative (such as "java\database"). Once you have an object representing a folder, you can call its listFiles() method to see what's inside the folder. This method returns an array of File objects representing every file and subfolder it contains.

Reading Data from a Stream

The first project of the hour is to read data from a file using an input stream. You can do this using the FileInputStream class, which represents input streams that will be read as bytes from a file. You can create a file input stream by specifying a filename or a File object as the argument to the FileInputStream() constructor method. The file must exist before the file input stream is created. If it doesn't, an IOException will be generated when you try to create the stream. Many of the methods associated with reading and writing files will generate this exception, so it's often convenient to put all statements involving the file in their own TRy-catch block, as in this example:

try {
 File cookie = new File("cookie.web");
 FileInputStream = new FileInputStream(cookie);
 System.out.println("Length of file: " + cookie.length());
} catch (IOException e) {
 System.out.println("Could not read file.");
}


File input streams read data in bytes. You can read a single byte by calling the stream's read() method without an argument. If no more bytes are available in the stream because you have reached the end of the file, a byte value of -1 will be returned. When you read an input stream, it begins with the first byte in the stream—such as the first byte in a file. You can skip some bytes in a stream by calling its skip() method with one argument: an int representing the number of bytes to skip. The following statement skips the next 1024 bytes in a stream named scanData:

scanData.skip(1024);


If you want to read more than one byte at a time, do the following:

  • Create a byte array that is exactly the size of the number of bytes you want to read.
  • Call the stream's read() method with that array as an argument. The array will be filled with bytes read from the stream.

You will create an app that reads ID3 data from an MP3 audio file. Because MP3 is such a popular format for music files, 128 bytes are often added to the end of an ID3 file to hold information about the song, such as the title, artist, and album it is from. The ReadID3 app reads an MP3 file using a file input stream, skipping everything but the last 128 bytes. The remaining bytes are examined to see whether they contain ID3 data. If they do, the first three bytes will be the numbers 84, 65, and 71.

By the Way

On the ASCII character set, which is included in the Unicode Standard character set supported by Java, those three numbers represent the capital letters "T," "A," and "G," respectively.


Open the editor you have been using, and enter the text of Listing 20.1. Save the file as ReadID3.java.

Listing 20.1. The Full Text of ReadID3.java
 1: import java.io.*;
 2:
 3: public class ReadID3 {
 4: public static void main(String[] arguments) {
 5: try {
 6: File song = new File(arguments[0]);
 7: FileInputStream file = new FileInputStream(song);
 8: int size = (int)song.length();
 9: file.skip(size - 128);
10: byte[] last128 = new byte[128];
11: file.read(last128);
12: String id3 = new String(last128);
13: String tag = id3.substring(0, 3);
14: if (tag.equals("TAG")) {
15: System.out.println("Title: " + id3.substring(3, 32));
16: System.out.println("Artist: " + id3.substring(33, 62));
17: System.out.println("Album: " + id3.substring(63, 91));
18: System.out.println("Year: " + id3.substring(93, 97));
19: } else {
20: System.out.println(arguments[0] + " does not contain"
21: + " ID3 info.");
22: }
23: file.close();
24: } catch (Exception e) {
25: System.out.println("Error -- " + e.toString());
26: }
27: }
28: }


When you compile this source file, a ReadID3 class file will be created. You can run this class as an app, specifying an MP3 file on your system as a command-line argument. For example:

java ReadID3 "Perfect Silence.mp3"


If you have the song Perfect Silence.mp3 on your system (and you really should), here's an example of what the ReadID3 app will display:

Title: Perfect Silence Artist: Scapegoat Wax Album: Swax Year: 2002


Did you Know?

If you don't have Perfect Silence.mp3 on your computer (a big mistake, in my opinion), you can look for MP3 songs to examine using the Creative Commons that can be examined using Yahoo! Search at http://search.yahoo.com/cc. Creative Commons is a set of copyright licenses that stipulate how a work such as a song or tutorial can be distributed, edited, or republished. It's a great resource for finding music that's licensed for such use as playback in computer software.


The app reads the last 128 bytes from the MP3 in Lines 10–11 of Listing 20.1, storing them in a byte array. This array is used in Line 12 to create a String object that contains the characters represented by those bytes. If the first three characters in the string are "TAG," the MP3 file being examined contains ID3 information in a format the app understands. In Lines 15–18, the string's substring() method is called to display portions of the string. The characters to display are from the ID3 format, which always puts the artist, song, title, and year information in the same positions in the last 128 bytes of an MP3 file. Some MP3 files either don't contain ID3 information at all or contain ID3 information in a different format than the app can read. The file Perfect Silence.mp3 will contain readable ID3 information if you created it from a copy of the Swax CD that you downloaded, because programs that create MP3 files from audio CDs read song information from a music industry database called CDDB. After everything related to the ID3 information has been read from the MP3's file input stream, the stream is closed in Line 23. You should always close streams when you are finished with them to conserve resources in the Java interpreter.

By the Way

You may be tempted to find a copy of Perfect Silence.mp3 on a service such as AudioGalaxy, WinMX, or Gnutella, three of the most popular file-sharing services. I can understand this temptation perfectly where "Perfect Silence" is concerned. However, according to a recent report by the Recording Industry Association of America, anyone who downloads MP3 files for CDs you do not own will immediately burst into flame. For this reason, it's better just to buy the CD and make your own legal copy for personal use only. Swax is available at Sam Goody, Turtle's, Amazon.com and other leading retailers.


Buffered Input Streams

One of the ways to improve the performance of a program that reads input streams is to buffer the input. Buffering is the process of saving data in memory for use later when a program needs it. When a Java program needs data from a buffered input stream, it looks in the buffer first, which is faster than reading from a source such as a file. To use a buffered input stream, you create an input stream such as a FileInputStream object, then use that object to create a buffered stream. Call the BufferedInputStream(InputStream) constructor with the input stream as the only argument. Data will be buffered as it is read from the input stream. To read from a buffered stream, call its read() method with no arguments. An integer from 0 to 255 will be returned that represents the next byte of data in the stream. If no more bytes are available, -1 is returned instead. As a demonstration of buffered streams, the next program you create will add a feature to Java that many programmers miss from other languages they have used: console input. Console input is the ability to read characters from the console (also known as the command-line) while running an app. The System class, which contains the out variable used in the System.out.print() and System.out.println() statements, has a class variable called in that represents an InputStream object. This object receives input from the keyboard and makes it available as a stream. You can work with this input stream like any other. The following statement creates a buffered input stream associated with the System.in input stream:

BufferedInputStream bin = new BufferedInputStream(System.in);


The next project, the ReadConsole class, contains a class method you can use to receive console input in any of your Java apps. Enter the text of Listing 20.2 in your editor and save the file as ReadConsole.java.

Listing 20.2. The Full Text of ReadConsole.java
 1: import java.io.*;
 2:
 3: public class ReadConsole {
 4: public static String readLine() {
 5: StringBuffer response = new StringBuffer();
 6: try {
 7: BufferedInputStream bin = new
 8: BufferedInputStream(System.in);
 9: int in = 0;
10: char inChar;
11: do {
12: in = bin.read();
13: inChar = (char) in;
14: if (in != -1) {
15: response.append(inChar);
16: }
17: } while ((in != -1) & (inChar != '\n'));
18: bin.close();
19: return response.toString();
20: } catch (IOException e) {
21: System.out.println("Exception: " + e.getMessage());
22: return null;
23: }
24: }
25:
26: public static void main(String[] arguments) {
27: System.out.print("You are standing at the end of the road ");
28: System.out.print("before a small brick building. Around you ");
29: System.out.print("is a forest. A small stream flows out of ");
30: System.out.println("the building and down a gully.\n");
31: System.out.print("> ");
32: String input = ReadConsole.readLine();
33: System.out.println("That's not a verb I recognize.");
34: }
35: }


The ReadConsole class includes a main() method that demonstrates how it can be used. When you compile and run it as an app, the output should resemble the following:

You are standing at the end of the road before a small brick building.
Around you is a forest. A small stream flows out of the building and down a gully.
> go north That's not a verb I recognize.


The ReadConsole class contains one class method, readLine(), which receives characters from the console. When the Enter key is pressed, readLine() returns a String object that contains all the characters that were received. If you save the ReadConsole class in a folder that is listed in your CLASSPATH environment variable, you can call ReadConsole.readLine() from any Java program that you write.

By the Way

The ReadConsole class is also the world's least satisfying text adventure game. You can't enter the building, wade in the stream, or even wander off. For a more full-featured version of this game, which is called Adventure, visit the Interactive Fiction archive at http://www.wurb.com/if/game/1.


      
Comments