Recitation 2,3, weeks of 10, 17 September 2001: Reading from keyboard, reading and writing files There is a small assignment, due on September 25, at the end of this missive. The core Java language has no facilities for doing input/output (IO). Instead, these facilities are provided in the API classes, in package java.io. Weiss, section 2.6, pp. 50-55, "input and output", covers basically the same material, but we think you will find this introduction easier to comprehend. Read section 2.6. You need to know a bit about exceptions for full understanding; we'll cover them later. The classes mentioned here are java.lang.System, java.io.InputStreamReader, and java.io.BufferedReader. To be able to use them, put the following statement before the class in the .java file: import java.io.*; We also use class javax.swing.JFileChooser. To be able to use it, put the following statement before the class in the .java file: import javax.swing.*; The term "stream" is used for a sequence of data values that is processed --either read or written-- from beginning to end. When the data is being read, or the stream is an "input stream"; when it is being written, it is an "output stream". READING FROM THE KEYBOARD You have to do several steps to make your program respond to the keyboard. First, link to the keyboard. To do this, create an instance of class InputStreamReader: InputStreamReader isr= new InputStreamReader(System.in); System.in is a variable, which usually links to the keyboard. The problem with an InputStreamReader is that it reads only a byte at a time. A BufferedReader, created from the InputStreamReader, allows reading a line at a time: BufferedReader br= new BufferedReader(isr); You can now read a line at a time into a String variable line, using the statement line= br.readLine(); Method br.readLine() returns the next line of the file to which the BufferedReader is attached --it returns null if there is no such line. WARNING: Only one object should be linked to the keyboard (or any file) at a time. For example, don't have two BufferedReaders br1 and br2 linked to the keyboard and interleave expressions br1.readLine() and br2.readLine(). It won't work. IO EXCEPTIONS The code mentioned above won't work because of Java's ways for dealing with "exceptions". We won't go into exceptions now. Suffice it to say that each method that tries to link to a keyboard or read a line from a file must indicate that it "throws an I/O exception". We show how to do it below; note the clauses "throws IOException" on every method that may cause an IO exception to be "thrown" or that calls a method that may cause an IO exception to be "thrown". Again, we'll talk about exceptions pretty soon, in class. public class InputExample { public static void main(String[] args) throws IOException { ... readAndProcess(); ... } // Read and process two lines of keyboard input public static void readAndProcess() throws IOException { InputStreamReader isr= new InputStreamReader(System.in); BufferedReader br= new BufferedReader(isr); ... String line1= br.readLine(); process line1 ... String line2= br.readLine(); process line2 ... } } PARSING NUMBERS AND OTHER DATA Suppose String variable s contains a line of input (or any sequence of characters) and that the characters are known to contain the representation of an integer, e.g. " 35 " or "-154 " Extract the integer and store it in variable x (say) using the following statement. int x= Integer.parseInt(s.trim()); The call s.trim() yields s but with leading and trailing whitespace (blanks, tabs, etc.) removed. This allows the user to type blank characters before and after the integer. Here's the specification of method parseInt, from wrapper class Integer: // = an int value that equals s as a signed decimal integer; s may begin with // '-' to indicate a negative integer; all other characters of s must be decimal // digits; s may not contain whitespace. public static int parseInt(String s) throws NumberFormatException Similar methods exist in other wrapper classes: Byte.parseByte(s.trim()) yields a byte Short.parseShort(s.trim()) yields a short Integer.parseInteger(s.trim())) yields an int Long.parseLong(s.trim())) yields a long Float.parseFloat(s.trim())) yields a float Double.parseDouble(s.trim())) yields a double And each wrapper class has a corresponding method valueOf, which wraps the value: Byte.valueOf(s.trim()) yields a Byte Short.valueOf(s.trim()) yields a Short Integer.valueOf(s.trim())) yields an Int Long.valueOf(s.trim())) yields a Long Float.valueOf(s.trim())) yields a Float Double.valueOf(s.trim())) yields a Double READING FROM A FILE You can read from files, as well as from the keyboard. Of course, you have to know the name of the file that should be read --and perhaps the folder in which it is. You can get the file name from the user --have them type it on the keyboard. Once you have the name, you used it as an argument to a constructor of class FileReader --and then stream this into a Bufferedreader. Here's the code: public class TwoBRExample { // Print the length of each line in a file that is given to the program // by a user (through the keyboard) public static void main(String[] args) throws IOException { // Create and store in kbd a link to the keyboard BufferedReader kbd= new BufferedReader(new InputStreamReader(System.in)); // Get the file name from the user and store it in fName System.out.print("Enter a file name: "); String fName= kbd.readLine().trim(); // Create and store in fr a link to file fName BufferedReader fr= new BufferedReader(new FileReader(fName)); // Read file fr and print the length of each line String s= fr.readLine(); // {inv: s is the last line read and // the lengths of lines before line s have been printed} while (s != null) { System.out.println(s.length()); s= fr.readLine(); } } USING A DIALOG BOX TO GET THE FILE NAME FROM THE USER Having the reader type in a file name is too error-prone. We can use a dialog box to get the file name; the user can navigate to the right place in their file system and select the file to be read. We use an object of class JFileChooser, which is in the swing package. Here's a program that shows how to use it. When jd.showOpenDialog(null); is executed, the dialog window opens, and this statement terminates only after the user has navigated to a directory, selected a file, and closed the window. Then, evaluation of jd.getSelectedFile returns the file. // Illustrate use of class JFileChooser public class FileChooserApp { public static void main(String pars[]) throws IOException { BufferedReader fr= getReader(); // Read file fr and print the length of each line String s= fr.readLine(); // {invariant: s is the last line read and // the lengths of lines before line s have been printed} while (s != null) { System.out.println(s.length()); s= fr.readLine(); } } // Obtain a file name from the user, using a JFileChooser, // and return a reader that is linked to it. public static BufferedReader getReader() throws IOException { JFileChooser jd= new JFileChooser(); jd.setDialogTitle("Choose input file"); jd.showOpenDialog(null); return new BufferedReader(new FileReader(jd.getSelectedFile())); } } WRITING A FILE We used "new FileReader(f)" to get a FileReader attached to a file named f. We use the following to get an object (and store it in fos) that can be used to WRITE to f: FileOutPutStream fos= new FileOutputStream(f)); We can now pass fos as an an argument to a constructor of PrintStream to get an object that has methods to print on f: PrintStream ps= new PrintStream(fos); You can now write method calls like the following to write to f --this is just like using System.out.print and System.out.println: ps.print(5); ps.print(a < b); ps.println("The number is " + 5); BE CAREFUL IF YOU TRY TO WRITE A FILE; if a file with that name exists, you will overwrite the file. APPENDING TO A FILE Instead of overwriting a file, you can append to it. This is done using the following: FileOutPutStream fos= new FileOutputStream(f, true)); PrintStream ps= new PrintStream(fos); We use a different FileOutputStream constructor, with two arguments. If the second argument is false, the file will be written; if it is true, the file will be appended. IO Exceptions are suppressed in method ps.print and ps.println. When you are finished writing the file, you can check whether an I/I exception occurred by calling ps.checkError(); it returns true iff thee was an I/O error of some sort when using ps. Below is a program that obtains a file and appends two lines to it. About method jd.getSelectedFile. It returns an object of class java.io.File; then, the name is retreived from it. import java.io.*; import javax.swing.*; public class WriteExample { // Obtain a file name from the user using a JFileChooser // and append two lines to the file public static void main(String pars[]) throws IOException { String f= getFileName(); // Make an append link ps to file f PrintStream ps= new PrintStream(new FileOutputStream(f,true)); // Print two lines on the file ps.println("This is the first line written."); ps.println("This is the second line written."); System.out.println("File has been written"); } // Obtain a file name from the user, using a JFileChooser, // and return it public static String getFileName() throws IOException { JFileChooser jd= new JFileChooser(); jd.setDialogTitle("Choose input file"); jd.showOpenDialog(null); return jd.getSelectedFile().getPath(); } } ASSIGNMENT This material will sink in only if you exercise it. So here is a small assignment. Type in this program, and change the two lines that are written to something of your choice. Be sure to include the comments, and use the same indentation that the program below uses. Create by some other means a file with a few lines in it. Then run the program and have it append to the file that you just created. Print out the program AND the file, and submit both by 25 September, in class. The assignment will be graded on a pass-fail basis.