Certification Objective File Navigation and IO (Exam Objective 3.2)


Certification Objective —File Navigation and I/O (Exam Objective 3.2)

3.2 Given a scenario involving navigating file systems, reading from files, or writing to files, develop the correct solution using the following classes (sometimes in combination), from java.io: BufferedReader, BufferedWriter, File, FileReader, FileWriter, and PrintWriter.

I/O has had a strange history with the SCJP certification. It was included in all the versions of the exam up to and including 1.2, then removed from the 1.4 exam, and then re-introduced for Java 5.

I/O is a huge topic in general, and the Java APIs that deal with I/O in one fashion or another are correspondingly huge. A general discussion of I/O could include topics such as file I/O, console I/O, thread I/O, high-performance I/O, byte-oriented I/O, character-oriented I/O, I/O filtering and wrapping, serialization, and more. Luckily for us, the I/O topics included in the Java 5 exam are fairly well restricted to file I/O for characters, and serialization.

Here's a summary of the I/O classes you'll need to understand for the exam:

  • File The API says that the class File is "An abstract representation of file and directory pathnames." The File class isn't used to actually read or write data; it's used to work at a higher level, making new empty files, searching for files, deleting files, making directories, and working with paths.

  • FileReader This class is used to read character files. Its read() methods are fairly low-level, allowing you to read single characters, the whole stream of characters, or a fixed number of characters. FileReaders are usually wrapped by higher-level objects such as BufferedReaders, which improve performance and provide more convenient ways to work with the data.

  • BufferedReader This class is used to make lower-level Reader classes like FileReader more efficient and easier to use. Compared to FileReaders, BufferedReaders read relatively large chunks of data from a file at once, and keep this data in a buffer. When you ask for the next character or line of data, it is retrieved from the buffer, which minimizes the number of times that time-intensive, file read operations are performed. In addition, BufferedReader provides more convenient methods such as readLine(), that allow you to get the next line of characters from a file.

  • File Writer This class is used to write to character files. Its write() methods allow you to write character(s) or Strings to a file. FileWriters are usually wrapped by higher-level Writer objects such as BufferedWriters or PrintWriters, which provide better performance and higher-level, more flexible methods to write data.

  • BufferedWriter This class is used to make lower-level classes like FileWriters more efficient and easier to use. Compared to FileWriters, BufferedWriters write relatively large chunks of data to a file at once, minimizing the number of times that slow, file writing operations are performed. In addition, the BufferedWriter class provides a newLine() method that makes it easy to create platform-specific line separators automatically.

  • PrintWriter This class has been enhanced significantly in Java 5. Because of newly created methods and constructors (like building a PrintWriter with a File or a String), you might find that you can use PrintWriter in places where you previously needed a Writer to be wrapped with a FileWriter and/or a BufferedWriter. New methods like format(),printf(), and append() make PrintWriters very flexible and powerful.

image from book
Exam Watch

Stream classes are used to read and write bytes, and Readers and Writers are used to read and write characters. Since all of the file I/O on the exam is related to characters, if you see API class names containing the word "Stream", for instance DataOutputStream, then the question is probably about serialization, or something unrelated to the actual I/O objective.

image from book

Creating Files Using Class File

Objects of type File are used to represent the actual files (but not the data in the files) or directories that exist on a computer's physical disk. Just to make sure we're clear, when we talk about an object of type File, we'll say File, with a capital F. When we're talking about what exists on a hard drive, we'll call it a file with a lowercase f (unless it's a variable name in some code). Let's start with a few basic examples of creating files, writing to them, and reading from them. First, let's create a new file and write a few lines of data to it:

 import java.io.*;              // The Java 5 exam focuses on                                // classes from java.io class Writer1 {   public static void main(String [] args) {     File file = new File("fileWrite1.txt");    // There's no                                                // file yet!   } } 

If you compile and run this program, when you look at the contents of your current directory, you'll discover absolutely no indication of a file called fileWrite1.txt. When you make a new instance of the class File, you're not yet making an actual file, you're just creating a filename. Once you have a File object, there are several ways to make an actual file. Let's see what we can do with the File object we just made:

 import java.io.*; class Writer1 {  public static void main(String [] args) {    try {                        // warning: exceptions possible      boolean newFile = false;      File file = new File                // it's only an object                     ("fileWritel.txt");      System.out.println(file.exists());  // look for a real file      newFile = file.createNewFile();     // maybe create a file!      System.out.println(newFile);        // already there?      System.out.println(file.exists());  // look again }    } catch(IOException e) { }  } } 

This produces the output

 false true true 

And also produces an empty file in your current directory. If you run the code a second time you get the output

 true false true 

Let's examine these sets of output:

  • First execution The first call to exists() returned false, which we expectedremember new File() doesn't create a file on the disk! The createNewFile() method created an actual file, and returned true, indicating that a new file was created, and that one didn't already exist. Finally, we called exists() again, and this time it returned true, indicating that the file existed on the disk.

  • Second execution The first call to exists() returns true because we built the file during the first run. Then the call to createNewFile() returns false since the method didn't create a file this time through. Of course, the last call to exists() returns true.

A couple of other new things happened in this code. First, notice that we had to put our file creation code in a try/catch. This is true for almost all of the file I/O code you'll ever write. I/O is one of those inherently risky things. We're keeping it simple for now, and ignoring the exceptions, but we still need to follow the handleor-declare rule since most I/O methods declare checked exceptions. We'll talk more about I/O exceptions later. We used a couple of File's methods in this code:

  • boolean exists() This method returns true if it can find the actual file.

  • boolean createNewfile() This method creates a new file if it doesn't already exist.

image from book
Exam Watch

Remember, the exam creators are trying to jam as much code as they can into a small space, so in the previous example, instead of these three lines code,

 boolean newFile = false; ... newFile = file.createNewFile(); System.out.println(newFile); 

You might see something like the following single line of code, which is a bit harder to read, but accomplishes the same thing:

 System.out.println(file.createNewFile()); 

image from book

Using FileWriter and FileReader

In practice, you probably won't use the FileWriter and FileReader classes without wrapping them (more about "wrapping" very soon). That said, let's go ahead and do a little "naked" file I/O:

 import java.io.*; class Writer2 {   public static void main(String [] args) {     char[] in = new char[50];           // to store input     int size = 0;     try {       File file = new File(             // just an object                   "fileWrite2.txt");       FileWriter fw =                  new FileWriter(file);  // create an actual file                                         // & a FileWriter obj       fw.write("howdy\nfolks\n");       // write characters to                                         // the file       fw.flush();                       // flush before closing       fw.close();                       // close file when done       FileReader fr = =                  new FileReader(file);  // create a FileReader                                         // object       size = fr.read(in);               // read the whole file!       System.out.print(size + " ");     // how many bytes read       for(char c : in)                  // print the array         System.out.print(c);       fr.close();                       // again, always close }     } catch(IOException  e) { }   } } 

which produces the output:

 12 howdy folks 

Here's what just happened:

  1. FileWriter fw = new FileWriter(file) did three things:

    1. It created a FileWriter reference variable, fw.

    2. It created a FileWriter object, and assigned it to fw.

    3. It created an actual empty file out on the disk (and you can prove it).

  2. We wrote 12 characters to the file with the write() method, and we did a flush() and a close().

  3. We made a new FileReader object, which also opened the file on disk for reading.

  4. The read() method read the whole file, a character at a time, and put it into the char[] in.

  5. We printed out the number of characters we read size, and we looped through the in array printing out each character we read, then we closed the file.

Before we go any further let's talk about flush() and close(). When you write data out to a stream, some amount of buffering will occur, and you never know for sure exactly when the last of the data will actually be sent. You might perform many write operations on a stream before closing it, and invoking the flush() method guarantees that the last of the data you thought you had already written actually gets out to the file. Whenever you're done using a file, either reading it or writing to it, you should invoke the close() method. When you are doing file I/O you're using expensive and limited operating system resources, and so when you're done, invoking close() will free up those resources.

Now, back to our last example. This program certainly works, but it's painful in a couple of different ways:

  1. When we were writing data to the file, we manually inserted line separators (in this case \n), into our data.

  2. When we were reading data back in, we put it into a character array. It being an array and all, we had to declare its size beforehand, so we'd have been in trouble if we hadn't made it big enough! We could have read the data in one character at a time, looking for the end of file after each read(), but that's pretty painful too.

Because of these limitations, we'll typically want to use higher-level I/O classes like BufferedWriter or BufferedReader in combination with FileWriter or FileReader.

Combining I/O classes

Java's entire I/O system was designed around the idea of using several classes in combination. Combining I/O classes is sometimes called wrapping and sometimes called chaining. The java.io package contains about 50 classes, 10 interfaces, and 15 exceptions. Each class in the package has a very specific purpose (creating high cohesion), and the classes are designed to be combined with each other in countless ways, to handle a wide variety of situations.

When it's time to do some I/O in real life, you'll undoubtedly find yourself pouring over the java.io API, trying to figure out which classes you'll need, and how to hook them together. For the exam, you'll need to do the same thing, but we've artificially reduced the API. In terms of studying for exam Objective 3.2, we can imagine that the entire java.io package consisted of the classes listed in exam Objective 3.2, and summarized in Table 6-1, our mini I/O API.

Table 6-1: java.io Mini API

java.io Class

Extends From

Key Constructor(s) Arguments

Key Methods

File

Object

File, String

String

String, String

createNewFile()

delete()

exists()

isDirectory()

isFile()

list()

mkdir()

renameTo()

FileWriter

Writer

File

String

close()

flush()

write()

BufferedWriter

Writer

Writer

close()

flush()

newLine()

write()

PrintWriter

Writer

File (as of Java 5)

String (as of Java 5)

OutputStream

Writer

close()

flush()

format()*, printf()*

print(), println()

write()

FileReader

Reader

File

String

read()

BufferedReader

Reader

Reader

read()

readLine()

   

*Discussed later

Now let's say that we want to find a less painful way to write data to a file and read the file's contents back into memory. Starting with the task of writing data to a file, here's a process for determining what classes we'll need, and how we'll hook them together:

  1. We know that ultimately we want to hook to a File object. So whatever other class or classes we use, one of them must have a constructor that takes an object of type File.

  2. Find a method that sounds like the most powerful, easiest way to accomplish the task. When we look at Table 6-1 we can see that BufferedWriter has a newLine() method. That sounds a little better than having to manually embed a separator after each line, but if we look further we see that PrintWriter has a method called println(). That sounds like the easiest approach of all, so we'll go with it.

  3. When we look at PrintWriter's constructors, we see that we can build a PrintWriter object if we have an object of type file, so all we need to do to create a PrintWriter object is the following:

     File file = new File("fileWrite2.txt");   // create a File PrintWriter pw = new PrintWriter(file);   // pass file to                                           // the PrintWriter                                           // constructor 

Okay, time for a pop quiz. Prior to Java 5, PrintWriter did not have constructors that took either a String or a File. If you were writing some I/O code in Java 1.4, how would you get a PrintWriter to write data to a File? Hint: You can figure this out by studying the mini I/O API, Table 6-1.

Here's one way to go about solving this puzzle: First, we know that we'll create a File object on one end of the chain, and that we want a PrintWriter object on the other end. We can see in Table 6-1 that a PrintWriter can also be built using a Writer object. Although Writer isn't a class we see in the table, we can see that several other classes extend Writer, which for our purposes is just as good; any class that extends Writer is a candidate. Looking further, we can see that FileWriter has the two attributes we're looking for:

  1. It can be constructed using a File.

  2. It extends Writer.

Given all of this information, we can put together the following code (remember, this is a Java 1.4 example):

 File file = new File("fileWrite2.txt");  // create a File object FileWriter fw = new FileWriter(file);    // create a FileWriter                                          // that will send its                                          // output to a File PrintWriter pw = new PrintWriter(fw);    // create a PrintWriter                                          // that will send its                                          // output to a  Writer pw.println("howdy");                    // write the data pw.println("folks"); 

At this point it should be fairly easy to put together the code to more easily read data from the file back into memory. Again, looking through the table, we see a method called readLine() that sounds like a much better way to read data. Going through a similar process we get the following code:

 File file =       new File("fileWrite2.txt");  // create a File object AND                                    // open "fileWrite2.txt" FileReader fr =           new FileReader(file);    // create a FileReader to get                                    // data from 'file' BufferedReader br =          new BufferedReader(fr);   // create a BufferReader to                                    // get its data from a Reader String data = br.readLine();       // read some data 

image from book
Exam Watch

You're almost certain to encounter exam questions that test your knowledge of how HO classes can be chained. If you're not totally clear on this last section, we recommend that you use Table 6-1 as a reference, and write code to experiment with which chaining combinations are legal and which are illegal.

image from book

Working with Files and Directories

Earlier we touched on the fact that the File class is used to create files and directories. In addition, File's methods can be used to delete files, rename files, determine whether files exist, create temporary files, change a file's attributes, and differentiate between files and directories. A point that is often confusing is that an object of type File is used to represent either a file or a directory. We'll talk about both cases next.

We saw earlier that the statement

 File file = new File("foo"); 

always creates a File object, and then does one of two things:

  1. If "foo" does NOT exist, no actual file is created.

  2. If "foo" does exist, the new File object refers to the existing file.

Notice that File file = new File ("foo"); NEVER creates an actual file. There are two ways to create a file:

  1. Invoke the createNewFile() method on a File object. For example:

     File file = new File("foo");    // no file yet file.createNewFile();           // make a file, "foo" which                                 // is assigned to 'file' 

  2. Create a Reader or a Writer or a Stream. Specifically, create a FileReader, a FileWriter, a PrintWriter, a FileInputStream, or a FileOutputStream. Whenever you create an instance of one of these classes, you automatically create a file, unless one already exists, for instance

     File file = new File("foo"); // no file yet PrintWriter pw =     new PrintWriter(file);   // make a PrintWriter object AND                              // make a file, "foo" to which                              // 'file' is assigned, AND assign                              // 'pw' to the PrintWriter 

Creating a directory is similar to creating a file. Again, we'll use the convention of referring to an object of type File that represents an actual directory, as a Directory File object, capital D, (even though it's of type File.) We'll call an actual directory on a computer a directory, small d. Phew! As with creating a file, creating a directory is a two-step process; first we create a Directory (File) object, then we create an actual directory using the following mkdir() method:

 File myDir = new File("mydir");   // create an object myDir.mkdir();                    // create an actual directory 

Once you've got a directory, you put files into it, and work with those files:

 File myFile = new File(myDir, "myFile.txt"); myFile.createNewFile(); 

This code is making a new file in a subdirectory. Since you provide the subdirectory to the constructor, from then on you just refer to the file by its reference variable. In this case, here's a way that you could write some data to the file myFile:

 PrintWriter pw = new Printwriter(myFile); pw.println("new stuff"); pw.flush(); pw.close(); 

Be careful when you're creating new directories! As we've seen, constructing a Reader or Writer will automatically create a file for you if one doesn't exist, but that's not true for a directory:

 File myDir = new File("mydir"); // myDir.mkdir();                   // call to mkdir() omitted! File myFile = new File(               myDir, "myFile.txt"); myFile.createNewFile();             // exception if no mkdir! 

This will generate an exception something like

 java.io.IOException: No such file or directory 

You can refer a File object to an existing file or directory. For example, assume that we already have a subdirectory called existingDir in which resides an existing file existingDirFile.txt, which contains several lines of text. When you run the following code,

 File existingDir = new File("existingDir");     // assign a dir System.out.println(existingDir.isDirectory()); File existingDirFile = new File(           existingDir, "existingDirFile.txt");  // assign a file System.out.println (existingDirFile.isFile()); FileReader fr = new FileReader(existingDirFile}; BufferedReader br = new BufferedReader(fr);     // make a Reader String s; while( (s = br.readLine()) != null)             // read data   System.out.printIn(s); br.close(); 

the following output will be generated:

 true true existing sub-dir data line 2 of text line 3 of text 

Take special note of the what the readLine() method returns. When there is no more data to read, readLine() returns a null—this is our signal to stop reading the file. Also, notice that we didn't invoke a flush() method. When reading a file, no flushing is required, so you won't even find a flush() method in a Reader kind of class.

In addition to creating files, the File class also let's you do things like renaming and deleting files. The following code demonstrates a few of the most common ins and outs of deleting files and directories (via delete()), and renaming files and directories (via renameTo()):

 File delDir = new File("deldir");       // make a directory delDir.mkdir(); File delFile1 = new File(            delDir, "delFile1.txt");     // add file to directory delFile1.createNewFile(); File delFile2 = new File(            delDir, "delFile2.txt");     // add file to directory delFile2.createNewFile(); delFile1.delete();                      // delete a file System.out.println("delDir is "                   + delDir.delete());   // attempt to delete                                         // the directory File newName = new File(            delDir, "newName.txt");      // a new object delFile2.renameTo(newName);             // rename file File newDir = new File("newDir");       // rename directory delDir.renameTo(newDir); 

This outputs

 delDir is false 

and leaves us with a directory called newDir that contains a file called newName.txt. Here are some rules that we can deduce from this result:

  • delete() You can't delete a directory if it's not empty, which is why the invocation delDir.delete() failed.

  • renameTo() You must give the existing File object a valid new File object with the new name that you want. (If newName had been null we would have gotten a NullPointerException.)

  • renameTo() It's okay to rename a directory, even if it isn't empty.

There's a lot more to learn about using the java.io package, but as far as the exam goes we only have one more thing to discuss, and that is how to search for a file. Assuming that we have a directory named searchThis that we want to search through, the following code uses the File.list() method to create a String array of files and directories, which we then use the enhanced for loop to iterate through and print:

 String[] files = new String[100]; File search = new File("searchThis"); files = search.list();                 // create the list for(String fn : files)                 // iterate through it   System.out.println("found " + fn); 

On our system, we got the following output:

 found dir1 found dir2 found dir3 found file1.txt found file2.txt 

Your results will almost certainly vary : )

In this section we've scratched the surface of what's available in the java.io package. Entire books have been written about this package, so we're obviously covering only a very small (but frequently used) portion of the API. On the other hand, if you understand everything we've covered in this section, you will be in great shape to handle any java.io questions you encounter on the exam (except for serialization, which is covered in the next section).




SCJP Sun Certified Programmer for Java 5 Study Guide Exam 310-055
SCJP Sun Certified Programmer for Java 5 Study Guide (Exam 310-055) (Certification Press)
ISBN: 0072253606
EAN: 2147483647
Year: 2006
Pages: 131

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net