File Input/output using Loops Frequently a program will require more input data than can simply be input by a user at the keyboard. Data files can be prepared ahead of time using a text editor, stored on disk, and accessed via an executing program. Also, instead of writing output to the screen, a program can direct that that output be stored in a file and saved to disk. There are many classes provided by Java that help us read from & write to files. The simplest are classes Scanner and PrintWriter. Before we cover these classes in detail, we need to review some terms and concepts involved with files: Binary and Text Files: A binary file stores data in the format that it is stored in memory, in an internal format. A text file stores data as ASCII values, in a human readable format. When data is stored in binary format it does not need to be translated to or from ASCII and therefore data transfer is more efficient and optimized. Stream I/O: This refers to the manner in which data is stored in ( or output to) a file. With stream I/O data is read as a series of continuous characters or bytes. This is how input from the keyboard is read. Two classes FileInputStream and FileOutputStream are used to access streams of bytes. Classes Reader and Writer and their descendents (FileReader and PrintWriter) are used to access streams of characters. We will learn to use class PrintWriter to write data to an output file, and class FileReader to allow Scanner to read input from a file. Buffered Stream: rather than read one character/byte at a time from the input stream (or write one character/byte at a time to the output stream), we can create a “buffered stream” in memory whereby a “block” of stream data is written to (or read from) the buffer, before any actual access to the input/output device occurs. We can think of the buffer, as a container in memory into which our data is written. When the buffer is full, it’s contents are written to the device. This allows multiple reads/writes to occur from the buffer, between actual accesses to the device, thus speeding up the process of I/O again. Reading input from a File To read data from a file, the programmer needs to have an understanding of the format of the data stored in the file, and its meaning. Often each physical line will contain multiple values which together form a logical record. A logical record is a set of values which together describe one unique entity in our problem space. For example if we have a data file containing information about employees and their hours worked it might look like the following: Smith John 37.5 7.35 s b Lewis Larry 45.0 5.50 s n Jones James 55.0 8.20 m b In this example, the first string represents the employee’s last name, followed by first name, number of hours worked, hourly wage, whether single or married, and whether or not they are benefits eligible. So the first line would translate into: John Smith, who worked 37.5 hours, at $7.35 per hour, single, and is benefits eligible. We would write our program to read and process the data one line at a time. Class Scanner offers many methods that you can use with loops to read multiple “lines” of data from a data file. In addition to the methods that accomplish input, nextInt(), nextFloat() etcetera that would be used to access individual values, Class Scanner offers others that manipulate and test the contents of a file so that we do not attempt to read beyond the end of the physical file, these include: o hasNext(): This method returns true if the data file has more data of any type that has not yet been read. o hasNextInt(): returns true if the next value in the data file can be interpreted as an Integer. o hasNexttFloat(): returns true if the next value in the data file can be interpreted as an floating point number. o hasNextBoolean(): returns true if the next value in the data file can be interpreted as a Boolean value. o hasNextLine(): returns true if there is another line of input in the file. Using one of these methods, as we can create a loop to read data from a file, until all of that data is exhausted. For example, if we have defined a Scanner instance, called console the logic of our program would look like: while (console.hasNext()) { //READ THE DATA FROM THAT LINE // PROCESS THE DATA } We could also use the method hasNextLine() To allow Scanner to access a physical file, we must use class FileReader. Instances of this class are used to create a connection between out program and a physical file: Scanner infile = new Scanner(new FileReader(“c:\\mydata.txt”); Our Program mydata.txt Any methods from Scanner that are invoked for “infile” are now applied to the data file, rather than the console. In the previous example the instance of FileReader is created when we create the instance of scanner, there are other ways: FileReader name = new FileReader(“path and file name”); We can define instance of FileReader separately, the filename given may or may not include the full path, if only a file name is given ie: “mydata.txt” then the file is assumed to be in the current working directory. You will notice something unusual in the first example which included a path name. The character \ is a control character in Java, as in \n which is the new line character. If it is part of a path name, then we must use two \\. So for example: “c:\\cs110\\inputdata\\mydata.txt”. Once the instance of FileReader is declared, we can then use it in the declaration of our scanner: FileReader inputFile = new FileReader( “mydata.txt”); Scanner infile = new Scanner(inputFile); Please Note: when declaring an instance of FileReader, the file name can also be specified using a string variable, instead of a quoted literal. Which means it is possible to prompt the user for the name of the input file, read it into a string variable, and then create our connection to the file. String filename; Scanner console = new Scanner(System.in); System.out.println(“Please enter the name of the file to be read. “); filename = console.nextLine(); FileReader inputFile = new FileReader(filename); Scanner input = new Scanner(inputFile); There is a possible error condition for which we must account in case the file we are accessing does not exist. This is a “file not found” exception, and this causes Java to raise or “throw” an IOException. What does that mean anyway???? There are two types of exceptions thrown by Java methods: checked and unchecked. An exception is an error that occurs during the execution of a program.. When you call a method that throws a “checked” exception, the compiler checks to see that you don’t ignore it!!!!!!! “Checked” exceptions are caused by external circumstances that the programmer can not control or prevent. Therefore your program must provide a means of recognizing that these can occur and respond to them. Class FileReader throws checked exceptions. An unchecked exception typically arises from a mistake made by the programmer such as trying to read past the end of a string. The compiler does not check for these types of errors. We can write exception handlers which are special sections of code that are invoked when the error occurs, or we can choose to simply pass the error “back”, calling routine, which in this case to the operating system. We do this through using the “throws” specifier in our method heading for method main. import java.io.*; public class readingFiles1 { public static void main(String[] args) throws IOException { File infile = new File("c:\\cs110\\java\\indat.txt"); FileReader in = new FileReader(infile); } } If the file doesn’t exit this displays: Exception in thread "main" java.io.FileNotFoundException: c:\cs110\java\indat.txt (The system cannot find the file specified) at java.io.FileInputStream.open(Native Method) at java.io.FileInputStream.<init>(Unknown Source) at java.io.FileReader.<init>(Unknown Source) at readingFiles1.main(readingFiles1.java:7) The “throws” specifier just indicates that you KNOW that this exception might occur, and to terminate your method when this happens, passing the exception back to the CALLING program. The caller then needs to make the same decision – either handle the exception or pass it back to it’s calling program. Example of reading an input file; Write a program to read employee information from a data file, and calculate the employee’s weekly pay and withholdings using the following formula: grossPay = hours worked * hourly rate Tax = grossPay * taxRate If employee is married, taxRate = 17%, if single 13% If the employee is benefits eligible, benefitsCost = grossPay * 3%. netPay = grossPay –(benefitsCost + tax); // the format of the data file is lastname firstname hoursWorked Rate taxStatus BenefitsEligible import java.util.*; import java.io.*; public class payCheck { public static void main(String[] args) throws IOException { final float SINGLERATE = .13f; final float MARRIEDRATE = .17f; final float BENEFITSRATE = .03f; String last, first; char benefits, taxCode; float hoursWorked, rate, netPay, grossPay, tax=0, benefitsCost, taxRate; FileReader inputFile = new FileReader("employee.txt"); Scanner input = new Scanner(inputFile); System.out.printf("%-10s %-10s %-10s %-10s %-10s %-10s %-10s %-10s\n", "Last", "First", "Hours", "Pay", "Tax", "Benefits", "Gross", "Net"); System.out.printf("%-10s %-10s %-10s %-10s %-10s %-10s %-10s %-10s\n\n","Name", "Name", "Worked", "Rate", "Cost", "Cost", "Pay", "Pay"); while (input.hasNextLine()) { last = input.next(); first = input.next(); hoursWorked = input.nextFloat(); rate = input.nextFloat(); taxCode = (input.next()).charAt(0); benefits = (input.next()).charAt(0); grossPay = hoursWorked * rate; if (taxCode == 's' || taxCode == 'S') tax = grossPay *SINGLERATE; else if (taxCode == 'm' || taxCode == 'M') tax = grossPay * MARRIEDRATE; else System.out.println("INVALID TAXCODE"); if (benefits == 'b' || benefits == 'B') benefitsCost = grossPay * BENEFITSRATE; else benefitsCost = 0; netPay = grossPay - (benefitsCost + tax); System.out.printf("%-10s %-10s %-10.2f %-10.2f %-10.2f %-10.2f %-10.2f %-10.2f\n", last, first, hoursWorked, rate, tax, benefitsCost, grossPay, netPay);__ }// end of while } } When run with the data file: Smith John 37.5 7.35 s b Lewis Larry 45.0 5.50 s n Jones James 55.0 8.20 m b The output is : Last Name First Name Hours Worked Pay Rate Tax Cost Benefits Cost Gross Pay Net Pay Smith Lewis Jones John Larry James 37.50 45.00 55.00 7.35 5.50 8.20 35.83 32.17 76.67 8.27 0.00 13.53 275.63 247.50 451.00 231.52 215.32 360.80 Writing Data to an output file The simplest method to write to a text file using stream I/O is by using the class PrintWriter. We declare an instance of PrintWriter by: PrintWriter identifier = new PrintWriter(file name string or file instance); ie: PrintWriter out = new PrintWriter(“c:\\cs110\\java\\output.dat”); File outFile = new File(“c:\\cs110\\java\\output.txt”); PrintWriter output = new PrintWriter(outFile); This opens an output stream associated with the indicated file. IF the file already exists it is emptied before new data is written into it. IF it does not exist it is created. PrintWriter allows us to use the print, println, and printf methods with which we are already familiar. Other methods we should be familiar with are: o close() -- This methods closes the output stream. It must be performed on an output file before the method terminates, otherwise the output file may be corrupted. o flush() -- Flushes any remaining data in the internal buffer to the output file. import java.io.*; public class testOutput2 { public static void main(String[] args) throws IOException { File outFile = new File("c:\\cs110\\java\\outputtest.dat"); PrintWriter out = new PrintWriter(outFile); char aChar = 'A'; for (int j=0; j < 10; j++){ for(int i=0; i<30; i++)out.print(aChar); out.println(); aChar++; } // end for out.flush(); out.close(); }// end main } Sample output AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ Class File, and file operations In all of the previous examples we have assumed that the input or output file name was valid, or simply passed the error off to the operating system. There is another class in Java “File” which allows us to create a connection to a file or directory, and then test it before creating an instance of FileReader or PrintWriter. Class “File” in package “java.io” allows us to associate a “file” object to a physical file or a directory. Once the connection is created, we can do many useful operations on the file, such as checking that it exists, deleting it, or even searching a directory. The constructor for class FILE has several form : (1) File myFile = new File(“c:\\cs110\\java\\xyz.txt”); is a fully qualified reference to a file containing an exact path. This creates file instance myFile and “associates” it with the text file. Note to specify slashes (as in windows) in a path name, you must use two slashes. Since ‘\’ is a control character. (2) File myFile = new File(“c:\\cs110\\java”, “xyz.txt”); There is also a variation of the constructor for “File” that separates the “path” from the “file name”; (3) File myDir = new File (“c:\\cs110\\java”); and File myFile = new File(myDir, “xyz.txt”); We can also declare an instance to the directory name separately from the file itself. This would simplify the access to multiple files in the same directory. Once we have a file instance (which is a File object), some of the operations we can perform on files are : exists() : This method tests the object to see if the physical file or directory exists. isDirectory() : Tests to see if the file instance points to a valid directory. isFile() : Tests to see if the object is a file. getAbsolutePath() : returns a string containing the absolute path to the file, consisting of the full path, and file name. delete(): deletes the file or directory associated with the file instance. list() : returns an array of strings listing all of the files and directories contained in the directory represented by the file instance. mkdir(): creates the directory represented by this abstract path name. Example 1: import java.io.*; public class testFileClass { public static void main(String[] args) { File inFile = new File("c:\\cs110\\java\\student.txt"); if ( inFile.exists() ) System.out.println("The file " + inFile.getAbsolutePath() + " exists!!"); else System.out.println("The file " + inFile.getAbsolutePath() + " does not exist!!"); File inFile2 = new File("c:\\cs110\\java\\student2.dat"); if ( inFile2.exists() ) System.out.println("The file " + inFile2.getAbsolutePath() + " exists!!"); else System.out.println("The file " + inFile2.getAbsolutePath() + " does not exist!!"); File myDir = new File("c:\\cs110\\java"); if ( myDir.isDirectory() ){ File myFile = new File(myDir, "student.txt"); System.out.println("The directory " + myDir.getAbsolutePath() + " exists, accessing file " + myFile.getAbsolutePath()); if (myFile.isFile()) System.out.println(myFile.getAbsolutePath() + " is a file. "); else System.out.println(myFile.getAbsolutePath() + " is not a file"); } else System.out.println(" Invalid directory name"); String [] fileList = myDir.list(); System.out.println("The files in " + myDir.getAbsolutePath() + " are:"); for (int i=0; i< fileList.length; i++) System.out.println(fileList[i]); } } The results of executing this program are: The file c:\cs110\java\student.txt exists!! The file c:\cs110\java\student2.dat does not exist!! The directory c:\cs110\java exists, accessing file c:\cs110\java\student.txt c:\cs110\java\student.txt is a file. The files in c:\cs110\java are: evaluating_expressions.doc h1p1.java h1p2.java h1p4.java student.txt testFileClass.class testFileClass.java