[ PDF ]
This laboratory has two parts. The first part presents the basic concepts related to Java I/O that you will need for your work (save these lecture notes and examples as they will be useful for your data structure course next year!). The second part requires modifying an application called the PlayListManager for reading and writing songs from/to a file.
This document introduces the basic elements of the input/output (I/O) system in Java. In particular, it covers a subset of the classes that are found in the java.io package. Since Java 1.4, a new package has been introduced, java.nio (new io), that defines more advanced concepts, such as buffers, channels and memory mapping, these topics are not covered here.
Java I/O seems a bit complex at first. There are many classes. But also, objects from two or more classes need to be combined for solving specific tasks. Why is that? Java is a modern language that has been developed when the World Wide Web was becoming a reality. As such, the data can be written/read to/from many sources, including the console/keyboard, external devices (such as hard disks) or the network. The presence of the Web stimulated the creation of classes for handling various encodings of the information (English and European languages but also Arabic and Asian languages; and also binary data).
A stream is an ordered sequence of data that has a source or a destination. There are two major kinds of streams: character streams and byte streams.
In Java, characters are encoded with Unicodes — character streams are associated with text-based (human readable) I/O. The character streams are called readers and writers. This document focuses mainly on character streams. Byte streams are associated with data-based (binary) I/O. Examples of binary data include image and audio files, jpeg and mp3 files for example. Information can be read from an external source or written to an external source. Therefore, for (nearly) every input stream (or reader) there is a corresponding output stream (or writer). Besides input and output, there is a third access mode that is called direct access, which allows to read/write the data in any given order. This topic is only mentioned.
Several classes are found in the I/O package reflecting the fact that I/O involves two different kinds of streams and three different access modes.
Furthermore, the medium that is used (keyboard, console, disk, memory or network) dictates its own constraints (the requirement for a buffer or not, for instance). The I/O package contains some 50 classes, 10 interfaces and 15+ Exceptions. The large number of classes involved is enough to confuse a beginner programmer. On the top of this, generally objects from 2 or 3 classes need to be combined to carry out a basic task. Example.
where “data” is the name of the input file. The following sections are presenting the main concepts related to Java I/O. Most concepts are accompanied by simple examples and exercises, which illustrate specific topics. Compile and run all the examples. Complete all the exercises.
InputStream and OutputStream are two abstract classes that define the methods that are common to all input and output streams.
The class InputStream declares the following three methods.
Since InputStream is an abstract class it is never instantiated. Here are examples of its subclasses: AudioInputStream, ByteArrayInputStream, FileInputStream, FilterInputStream, ObjectInputStream, PipedInputStream, SequenceInputStream, StringBufferInputStream. One of the main InputStream classes that will be of interest to us is the FileInputStream, which obtains input bytes from a file in a file system, more on that later.
The class OutputStream declares the following three methods.
Since OutputStream is an abstract class, subclasses are used to create objects associated with specific types of I/O: ByteArrayOutputStream, FileOutputStream, FilterOutputStream, ObjectOutputStream and PipedOutputStream. FileOutputStream is commonly used. A file output stream is an output stream for writing data to a File.
Two objects are predefined and readily available for your programs. System.in is an input stream associated with the keyboard, and System.out is an output stream associated with the console.
Writing to (or reading from) a file generally involves three steps:
Closing the file is important to ensure that the data are written to the file, as well as to free the associated internal and external resources.
Let’s narrow down the discussion to reading from a file or reading from the keyboard.
Reading from a file involves creating a FileInputStream object. We’ll consider two constructors.
Having a File object allows for all sorts of operations, such as,
to name a few. FileInputStream is a direct subclass of InputStream. The methods that it provides allow to read bytes.
Because a FileInputStream allows to read bytes only, Java defines an InputStreamReader as bridge from byte to character streams. Its usage is as follows.
or
where System.in is generally associated with the keyboard of the terminal.
See Unicode.java and Keyboard.java.
or
Exercise 1 Write a class that reads characters for the keyboard using the method read( char[] b ); the maximum number of characters that can be read at a time is fixed and determined by the size of the buffer. Use the class Keyboard from the above example as a starting point.
Run some tests, do you notice anything bizarre? No matter how many characters you’ve entered the resulting String is always 256 characters long, furthermore, it contains several characters that are not printable. You must use the method trim to remove those non-printable characters.
See Keyboard.java.
For some applications, the input should be read one line at a time. For this, you will be using an object of the class BufferedReader. A BufferedReader uses an InputStreamReader to read the content. The InputStreamReader uses an InputStream to read the raw data. Each layer (object) adds new functionalities. The InputStream reads the data (bytes). The InputStreamStream converts the bytes to characters. Finally, the BufferedReader regroups the characters into groups, here lines.
or
See Copy.java — see Section Exception for further information about exception handling.
Exercise 2 Write a class that prints all the lines that contain a certain word. For each line containing the word, print the line number followed by the line itself.
Solution: Find.java
Exercise 3 Write a class that counts the number of occurrences of a given word within a file.
Exercise 4 Write a class that fetches the content of a Web page and prints it on the console.
Solution: WGet.java
Let’s narrow down the discussion to writing to the console and writing to a file. Notice the similarities with reading in; how the process is mirrored.
Writing to a file involves creating a FileOutputStream object. We’ll consider two constructors.
FileOutputStream is a direct subclass of OutputStream. The methods that it provides are for writing bytes.
or
System.err is the standard destination for error messages. The methods of an OutputStreamWriter are:
Exercise 5 Modify the class Copy.java so that it has a second argument that will be the name of a destination file. An accordingly, write a copy of the input to the output file.
Solution: Copy.java
Similarly, the following methods also terminate the current line by writing the line separator string (which varies from one operating system to the next).
This section revisits some of the concepts related to exception handling in Java and presents some of the concepts that are specific to Java I/O.
“Signals that an I/O exception of some sort has occurred. This class is the general class of exceptions produced by failed or interrupted I/O operations.” This is a direct subclass of Exception, this is therefore a checked exception that must be handled, caught by a catch clause or declared.
The constructor FileInputStream( String name ) throws an exception of type FileNotFoundException, which is a direct subclass of IOException. This exception must be handled, i.e. caught by a catch clause or declared.
The finally clause of a try statement is executed whether or not an exception was thrown. It is often used in constructions such as this one.
If pre() succeeds it will return an Object. In the finally clause, we know that pre() has succeeded if val is not null. Some post-processing operations can then be performed.
Here is an application of this idiom for closing a file, even if an error has occurred.
Most operating systems impose a limit on the maximum number of files that can be opened simultaneously; Linux sets this limit to 16 by default. When a large number of files need to be read, searching for some information on the entire disk space, it is imperative to close all the files that were opened as soon as possible.
Notice that the try statement has no catch clause, therefore both checked exceptions that can be thrown must be declared.
See Copy.java
Again here, the solution proposed by Java is more complex than with most other programming languages. Java has been developed recently compared to other languages. For example, C has been developed in the early 1970s. Java proposes solutions that allows to internationalize programs — using “.” or “,” to separate the decimals depending on the set up of the local computer where the program is executed, for example.
Format is the abstract superclass of all the formatting classes, namely DateFormat and NumberFormat. See what occurs when printing a floating point number (Test.java).
Sometimes this what you want and sometimes not (e.g. printing dollar amounts). In order to print only a fixed number of decimals, one needs to create a NumberFormat instance and tell this instance how to format numbers. First, the NumberFormat class is not imported by default, therefore you will have to import it into your program. Next, you will create and instance and then use the instance methods setMaximumFractionDigits and setMinimumFractionDigits to set the number of digits for the fractional part to 2. Finally, you can use the instance method format, of the number format object, to create the string representations. See Test.java
Alternatively, an Object of the class DecimalFormat, a direct subclass of NumberFormat, can be used. The following example prints a fractional number with 3 decimals and a width of 8 characters.
See Test.java
For this laboratory, you will modify the program PlayListManager to read and write Songs from and to a file.
Modify the PlayListManager to read Songs from a file. For example, the name of the input file can be specified on the command line,
The input file contains one entry per line. Each entry consists of the title of the Song, the name of the Artist and the title of the Album. The fields are separated by “:”.
Modify the PlayListManager to write the Songs from the new PlayList to a file. For example, the name of the output file can be specified on the command line,
The output file contains one entry per line. Each entry consists of the title of the Song, the name of the Artist and the title of the Album. The fields are separated by “:” (same as the input).
Suggestion: develop and test the methods PlayList getSongsFromFile( String fileName ) and void writeSongsToFile( String fileName ) in a separate class, say Utils. Once the methods are working add them to the PlayListManager application.
Last Modified: February 26, 2014