Random-Access Files

So far, we have seen how to create and manipulate sequential-access files. Sequential-access files are inappropriate for so-called instant-access applications, in which the desired information must be located immediately. Some popular instant-access applications are airline-reservation systems, banking systems, point-of-sale systems, automated teller machines (ATMs) and other kinds of transaction-processing systems that require rapid access to specific data. The bank at which you have your account may have hundreds of thousands, or even millions, of other customers, but when you use an ATM, the bank determines in seconds whether your account has sufficient funds for the transaction. This kind of instant access is possible with random-access files and with databases (Chapter 25). A program can access individual records of a random-access file directly (and quickly) without searching through other records. Random-access files are sometimes called direct-access files.

Recall from Section 14.5 that Java does not impose structure on a file, so an application that wants to use random-access files must specify the format of those files. Several techniques can be used to create random-access files. Perhaps the simplest is to require that all the records in a file be of the same fixed length. Using fixed-length records makes it easy for a program to calculate (as a function of the record size and the record key) the exact location of any record relative to the beginning of the file. We soon see how this capability facilitates direct access to specific records, even in large files.

Figure 14.22 illustrates Java's view of a random-access file composed of fixed-length records. (Each record in this figure is 100 bytes long.) A random-access file is like a railroad train with many carssome empty, some with contents.

Figure 14.22. Java's view of a random-access file.

A program can insert data in a random-access file without destroying the other data in the file. Similarly, a program can update or delete data stored previously without rewriting the entire file. In the following sections, we explain how to create a random-access file, enter data, read the data both sequentially and randomly, update the data and delete the data.

14.7.1. Creating a Random-Access File

A RandomAccessFile is useful for direct-access applications. With a sequential-access file, each successive input/output request reads or writes the next consecutive set of data in the file. With a random-access file, each successive input/output request could be directed to any part of the fileperhaps a section widely separated from the part referenced in the previous request. Direct-access applications provide rapid access to specific data items in large files, but users often have to wait for answers. In many instances, however, answers must be made available quickly, to prevent people from becoming impatient and taking their business elsewhere.

RandomAccessFile objects have all the capabilities of classes FileInputStream and FileOutputStream, as well as the capabilities described by interfaces DataInput and DataOutput. These interfaces provide methods for reading and writing primitive-type values, byte arrays and strings. When a program associates an object of class RandomAccessFile with a file, the program reads or writes data, beginning at the location in the file specified by the file-position pointer (the byte number of the next byte in the file to be read or written to), and manipulates all the data as primitive types. When writing an int value, four bytes are output to the file. When reading a double value, eight bytes are input from the file. The size of the types is guaranteed, because Java has fixed representations and sizes for all primitive types, regardless of the computing platform.

Random-access file-processing programs rarely write a single field to a file. Normally they write one object at a time, as we show in the upcoming examples. Consider the following problem:

Create a transaction-processing program capable of storing up to 100 fixed-length records for a company that can have up to 100 customers. Each record should consist of an account number (that will be used as the record key), a last name, a first name and a balance. The program should be able to update an account, insert a new account and delete an account.

The next several sections introduce the techniques necessary to create this credit-processing program. Figure 14.23 contains the RandomAccessAccountRecord class that is used by the next four programs for both reading records from and writing records to a file. Class RandomAccessAccountRecord inherits our AccountRecord implementation (Fig. 14.6), which includes private fieldsaccount, lastName, firstName and balanceas well as set and get methods for each field. We could inherit from either AccountRecord or AccountRecordSerializable. We do not use object serialization when processing random-access files in this chapter, so we inherit from class AccountRecord. Note that the class is in package com.deitel.jhtp6.ch14.

Figure 14.23. RandomAccessAccountRecord class used in random-access file programs.

(This item is displayed on pages 708 - 709 in the print version)

1 // Fig. 14.23: RandomAccessAccountRecord.java 2 // Subclass of AccountRecord for random-access file programs. 3 package com.deitel.jhtp6.ch14; // packaged for reuse 4 5 import java.io.RandomAccessFile; 6 import java.io.IOException; 7 8 public class RandomAccessAccountRecord extends AccountRecord 9 { 10 public static final int SIZE = 72; 11 12 // no-argument constructor calls other constructor with default values 13 public RandomAccessAccountRecord() 14 { 15 this ( 0, "", "", 0.0 ); 16 } // end no-argument RandomAccessAccountRecord constructor 17 18 // initialize a RandomAccessAccountRecord 19 public RandomAccessAccountRecord( int account, String firstName, 20 String lastName, double balance ) 21 { 22 super( account, firstName, lastName, balance ); 23 } // end four-argument RandomAccessAccountRecord constructor 24 25 // read a record from specified RandomAccessFile 26 public void read( RandomAccessFile file ) throws IOException 27 { 28 setAccount( file.readInt() ); 29 setFirstName( readName( file ) ); 30 setLastName( readName( file ) ); 31 setBalance( file.readDouble() ); 32 } // end method read 33 34 // ensure that name is proper length 35 private String readName( RandomAccessFile file ) throws IOException 36 { 37 char name[] = new char[ 15 ], temp; 38 39 for ( int count = 0; count < name.length; count++ ) 40 { 41 temp = file.readChar(); 42 name[ count ] = temp; 43 } // end for 44 45 return new String( name ).replace( '', ' ' ); 46 } // end method readName 47 48 // write a record to specified RandomAccessFile 49 public void write( RandomAccessFile file ) throws IOException 50 { 51 file.writeInt( getAccount() ); 52 writeName( file, getFirstName() ); 53 writeName( file, getLastName() ); 54 file.writeDouble( getBalance() ); 55 } // end method write 56 57 // write a name to file; maximum of 15 characters 58 private void writeName( RandomAccessFile file, String name ) 59 throws IOException 60 { 61 StringBuffer buffer = null; 62 63 if ( name != null ) 64 buffer = new StringBuffer( name ); 65 else 66 buffer = new StringBuffer( 15 ); 67 68 buffer.setLength( 15 ); 69 file.writeChars( buffer.toString() ); 70 } // end method writeName 71 } // end class RandomAccessAccountRecord

Finally, this example also introduces class StringBuffer, a class that allows us to dynamically manipulate strings. Class String provides many capabilities for processing strings. However, String objects are immutabletheir character contents cannot be changed after they are created. Class StringBuffer provides features for creating and manipulating dynamic string informationthat is, modifiable strings. Every StringBuffer is capable of storing a number of characters specified by its capacity. If the capacity of a StringBuffer is exceeded, the capacity is expanded to accommodate the additional characters. We use class StringBuffer to specify the size of a person's first or last name. We discuss class StringBuffer in more detail in Section 14.8, as well as in Chapter 29, Strings, Characters and Regular Expressions.

Line 10 declares the constant SIZE to represent the size, in bytes, of a record. A RandomAccessAccountRecord contains an int (4 bytes), two strings that we restrict to 15 characters each (30 bytes for the first name, 30 bytes for the last name) for this example and a double (8 bytes), for a total of 72 bytes.

Method read (lines 2632) reads one record from the RandomAccessFile specified as its argument. RandomAccessFile methods readInt (line 28) and readDouble (line 31) read the account and balance, respectively. Lines 2930 call utility method readName (lines 3546) twice to obtain the first and last names. Method readName reads 15 characters from the RandomAccessFile and returns a String. If a name is shorter than 15 characters, the extra characters have the default value ''the default for a char. Swing GUI components, such as JTextFields, cannot display null-byte charactersinstead, they display them as rectangles. Line 45 solves this problem by using String method replace to replace null bytes with spaces. Although our program does not use a GUI, we added this capability for those who wish to reuse this class in a GUI program.

Method write (lines 4955) outputs one record to the RandomAccessFile specified as its argument. This method uses RandomAccessFile method writeInt to output the integer account, method writeChars (called from utility method writeName in line 69) to output the firstName and lastName character arrays, and method writeDouble to output the double balance. [Note: To ensure that all the records in the RandomAccessFile are the same size, we write exactly 15 characters for the first name and exactly 15 for the last name.] Method writeName (lines 5870) performs the write operations for the first and last names. Lines 6366 create a StringBuffer that is initialized with either the name specified as an argument or with 15 to specify the size of the StringBuffer if name is null. Line 68 sets the number of characters in the StringBuffer. If a name is longer than 15 characters, it will be truncated to 15 characters. If a name is smaller than 15 characters it will be set to have 15 characters, with null characters ('') to fill the extra space.

Figure 14.24Fig. 14.25 illustrates the process of opening a random-access file and writing data to the disk. This program writes 100 blank RandomAccessAccountRecords. Each RandomAccessAccountRecord object contains 0 for the account number, null for the last name, null for the first name and 0.0 for the balance. The file is initialized to create the proper amount of "empty" space in which the account data will be stored and to enable us to determine in subsequent programs whether each record is empty or contains data.

Figure 14.24. Random-access file created sequentially.

(This item is displayed on page 711 in the print version)

1 // Fig. 14.24: CreateRandomFile.java 2 // Creates random-access file by writing 100 empty records to disk. 3 import java.io.IOException; 4 import java.io.RandomAccessFile; 5 6 import com.deitel.jhtp6.ch14.RandomAccessAccountRecord; 7 8 public class CreateRandomFile 9 { 10 private static final int NUMBER_RECORDS = 100; 11 12 // enable user to select file to open 13 public void createFile() 14 { 15 RandomAccessFile file = null; 16 17 try // open file for reading and writing 18 { 19 file = new RandomAccessFile( "clients.dat", "rw" ); 20 21 RandomAccessAccountRecord blankRecord = 22 new RandomAccessAccountRecord(); 23 24 // write 100 blank records 25 for ( int count = 0 ; count < NUMBER_RECORDS; count++ ) 26 blankRecord.write( file ); 27 28 // display message that file was created 29 System.out.println( "Created file clients.dat." ); 30 31 System.exit( 0 ); // terminate program 32 } // end try 33 catch ( IOException ioException ) 34 { 35 System.err.println( "Error processing file." ); 36 System.exit( 1 ); 37 } // end catch 38 finally 39 { 40 try 41 { 42 if ( file != null ) 43 file.close(); // close file 44 } // end try 45 catch ( IOException ioException ) 46 { 47 System.err.println( "Error closing file." ); 48 System.exit( 1 ); 49 } // end catch 50 } // end finally 51 } // end method createFile 52 } // end class CreateRandomFile

Figure 14.25. Testing class CreateRandomFile.

(This item is displayed on page 712 in the print version)

1 // Fig. 14.25: CreateRandomFileTest.java 2 // Testing class CreateRandomFile. 3 4 public class CreateRandomFileTest 5 { 6 public static void main( String args[] ) 7 { 8 CreateRandomFile application = new CreateRandomFile(); 9 application.createFile(); 10 } // end main 11 } // end class CreateRandomFileTest  

Created file clients.dat.  

Line 19 of Fig. 14.24 attempts to open a RandomAccessFile for use in this program. The RandomAccessFile constructor accepts two argumentsthe file name and the file-open mode. The file-open mode for a RandomAccessFile is either "r" (to open the file for reading) or "rw" (to open the file for reading and writing). Once again, we have used a new file extension (.dat). We use this file extension for binary files that do not use object serialization.

If an IOException occurs while opening the file, the program displays a message and terminates. If the file opens properly, lines 2526 invoke RandomAccessAccountRecord method write 100 times. This method causes the fields of object blankRecord to be written to the file associated with RandomAccessFile object file. Then line 43 closes the file. The code for closing the file is placed in it's own try statementif an attempt to close the file generates an IOException, this exception is caught in lines 4549. Figure 14.25 begins the execution of the program with method main (lines 610). Line 8 creates a CreateRandomFile object and line 9 calls its createFile method to create the file of 100 blank records.

14.7.2. Writing Data Randomly to a Random-Access File

The application in Fig. 14.26Fig. 14.27 writes data to a file that is opened with the "rw" mode (for reading and writing). It uses RandomAccessFile method seek to position to the exact location in the file at which a record of information is stored. Method seek sets the file-position pointer to a specific location in the file relative to the beginning of the file, and RandomAccessAccountRecord method write outputs the data at the current position in the file. The program assumes that the user does not enter duplicate account numbers.

Figure 14.26. Writing data to a random-access file.

(This item is displayed on pages 712 - 714 in the print version)

1 // Fig. 14.26: WriteRandomFile.java 2 // This program retrieves information from the user at the 3 // keyboard and writes the information to a random-access file. 4 import java.io.File; 5 import java.io.IOException; 6 import java.io.RandomAccessFile; 7 import java.util.NoSuchElementException; 8 import java.util.Scanner; 9 10 import com.deitel.jhtp6.ch14.RandomAccessAccountRecord; 11 12 public class WriteRandomFile 13 { 14 private RandomAccessFile output; 15 16 private static final int NUMBER_RECORDS = 100; 17 18 // enable user to choose file to open 19 public void openFile() 20 { 21 try // open file 22 { 23 output = new RandomAccessFile( "clients.dat", "rw" ); 24 } // end try 25 catch ( IOException ioException ) 26 { 27 System.err.println( "File does not exist." ); 28 } // end catch 29 } // end method openFile 30 31 // close file and terminate application 32 public void closeFile() 33 { 34 try // close file and exit 35 { 36 if ( output != null ) 37 output.close(); 38 } // end try 39 catch ( IOException ioException ) 40 { 41 System.err.println( "Error closing file." ); 42 System.exit( 1 ); 43 } // end catch 44 } // end method closeFile 45 46 // add records to file 47 public void addRecords() 48 { 49 // object to be written to file 50 RandomAccessAccountRecord record = new RandomAccessAccountRecord(); 51 52 int accountNumber = 0 ; // account number for AccountRecord object 53 String firstName; // first name for AccountRecord object 54 String lastName; // last name for AccountRecord object 55 double balance; // balance for AccountRecord object 56 57 Scanner input = new Scanner( System.in ); 58 59 System.out.printf( "%s %s %s %s ", 60 "To terminate input, type the end-of-file indicator ", 61 "when you are prompted to enter input.", 62 "On UNIX/Linux/Mac OS X type d then press Enter", 63 "On Windows type z then press Enter" ); 64 65 System.out.printf( "%s %s %s", "Enter account number (1-100),", 66 "first name, last name and balance.", "? " ); 67 68 while ( input.hasNext() ) // loop until end-of-file indicator 69 { 70 try // output values to file 71 { 72 accountNumber = input.nextInt(); // read account number 73 firstName = input.next(); // read first name 74 lastName = input.next(); // read last name 75 balance = input.nextDouble(); // read balance 76 77 if ( accountNumber > 0 && accountNumber <= NUMBER_RECORDS ) 78 { 79 record.setAccount( accountNumber ); 80 record.setFirstName( firstName ); 81 record.setLastName( lastName ); 82 record.setBalance( balance ); 83 84 output.seek( ( accountNumber - 1 ) * // position to proper 85 RandomAccessAccountRecord.SIZE ); // location for file 86 record.write( output ); 87 } // end if 88 else 89 System.out.println( "Account must be between 0 and 100." ); 90 } // end try 91 catch ( IOException ioException ) 92 { 93 System.err.println( "Error writing to file." ); 94 return; 95 } // end catch 96 catch ( NoSuchElementException elementException ) 97 { 98 System.err.println( "Invalid input. Please try again." ); 99 input.nextLine(); // discard input so user can try again 100 } // end catch 101 102 System.out.printf( "%s %s %s", "Enter account number (1-100),", 103 "first name, last name and balance.", "? " ); 104 } // end while 105 } // end method addRecords 106 } // end class WriteRandomFile

Figure 14.27. Testing class WriteRandomFile.

(This item is displayed on page 715 in the print version)

1 // Fig. 14.27: WriteRandomFileTest.java 2 // This program tests class WriteRandomFile. 3 4 public class WriteRandomFileTest 5 { 6 public static void main( String args[] ) 7 { 8 WriteRandomFile application = new WriteRandomFile(); 9 application.openFile(); 10 application.addRecords(); 11 application.closeFile(); 12 } // end main 13 } // end class WriteRandomFileTest  

To terminate input, type the end-of-file indicator when you are prompted to enter input. On UNIX/Linux/Mac OS X type d then press Enter On Windows type z then press Enter Enter account number (1-100), first name, last name and balance. ? 37 Doug Barker 0.00 Enter account number (1-100), first name, last name and balance. ? 29 Nancy Brown -24.54 Enter account number (1-100), first name, last name and balance. ? 96 Sam Stone 34.98 Enter account number (1-100), first name, last name and balance. ? 88 Dave Smith 258.34 Enter account number (1-100), first name, last name and balance. ? 33 Stacey Dunn 314.33 Enter account number (1-100), first name, last name and balance. ? ^Z  

The user enters values for the account number, first name, last name and balance. After each record is entered, the program stores the data in RandomAccessAccountRecord object record (lines 7982 of Figure 14.26) and calls record's write method to output the data (line 86).

Lines 8485 call RandomAccessFile method seek to position the file-position pointer for object output to the byte location calculated by ( accountNumber - 1 ) * RandomAccessAccountRecord. SIZE. Account numbers in this program are in the range 1100. We subtract one from the account number when calculating the byte location of the record. Thus, for record one, the file-position pointer is set to byte zero of the file. For record 100, the file-position pointer is set to skip the first 99 records in the file.

14.7.3. Reading Data Sequentially from a Random-Access File

In the preceding sections, we created a random-access file and wrote data to it. In this section, we develop a program (Fig. 14.28Fig. 14.29) that opens a RandomAccessFile for reading with the "r" file-open mode (line 19 of Fig. 14.28), reads through the file sequentially and displays only those records containing data. The program produces an additional benefit. See whether you can determine what it iswe reveal it at the end of this section.

Figure 14.28. Reading data sequentially from a random-access file.

(This item is displayed on pages 716 - 717 in the print version)

1 // Fig. 14.28: ReadRandomFile.java 2 // This program reads a random-access file sequentially and 3 // displays the contents one record at a time in text fields. 4 import java.io.EOFException; 5 import java.io.IOException; 6 import java.io.RandomAccessFile; 7 8 import com.deitel.jhtp6.ch14.RandomAccessAccountRecord; 9 10 public class ReadRandomFile 11 { 12 private RandomAccessFile input; 13 14 // enable user to select file to open 15 public void openFile() 16 { 17 try // open file 18 { 19 input = new RandomAccessFile( "clients.dat", "r" ); 20 } // end try 21 catch ( IOException ioException ) 22 { 23 System.err.println( "File does not exist." ); 24 } // end catch 25 } // end method openFile 26 27 // read and display records 28 public void readRecords() 29 { 30 RandomAccessAccountRecord record = new RandomAccessAccountRecord(); 31 32 System.out.printf( "%-10s%-15s%-15s%10s ", "Account", 33 "First Name", "Last Name", "Balance" ); 34 35 try // read a record and display 36 { 37 while ( true ) 38 { 39 do 40 { 41 record.read( input ); 42 } while ( record.getAccount() == 0 ); 43 44 // display record contents 45 System.out.printf( "%-10d%-12s%-12s%10.2f ", 46 record.getAccount(), record.getFirstName(), 47 record.getLastName(), record.getBalance() ); 48 } // end while 49 } // end try 50 catch ( EOFException eofException ) // close file 51 { 52 return; // end of file was reached 53 } // end catch 54 catch ( IOException ioException ) 55 { 56 System.err.println( "Error reading file." ); 57 System.exit( 1 ); 58 } // end catch 59 } // end method readRecords 60 61 // close file and terminate application 62 public void closeFile() 63 { 64 try // close file and exit 65 { 66 if ( input != null ) 67 input.close(); 68 } // end try 69 catch ( IOException ioException ) 70 { 71 System.err.println( "Error closing file." ); 72 System.exit( 1 ); 73 } // end catch 74 } // end method closeFile 75 } // end class ReadRandomFile

Figure 14.29. Testing class ReadRandomFile.

(This item is displayed on page 717 in the print version)

1 // Fig. 14.29: ReadRandomFileTest.java 2 // Testing class ReadRandomFile. 3 4 public class ReadRandomFileTest 5 { 6 public static void main( String args[] ) 7 { 8 ReadRandomFile application = new ReadRandomFile(); 9 application.openFile(); 10 application.readRecords(); 11 application.closeFile(); 12 } // end main 13 } // end class ReadRandomFileTest  

Account First Name Last Name Balance 29 Nancy Brown -24.54 33 Stacey Dunn 314.33 37 Doug Barker 0.00 88 Dave Smith 258.34 96 Sam Stone 34.98  

Good Programming Practice 14.1

Open a file with the "r" file-open mode for input if the contents should not be modified. This practice prevents unintentional modification of the file's contents. This is another example of the principle of least privilege.

The program reads records by invoking method readRecords (lines 2859). This method invokes class RandomAccessAccountRecord's read method (line 41) to read one record's data into RandomAccessAccountRecord object record. Method readRecords reads from the file using two loops. The outer loop, a while statement, loops until an attempt is made to read past the end of the file. The inner loop, a do...while statement, is used to read records until one is encountered with a nonzero account number (zero is the account number for empty records). At this point, the record is displayed. When all the records have been read, the file is closed and the program terminates. Figure 14.29 contains method main and begins the execution of the program. Lines 911 open the file, call method readRecords and close the file.

What about that additional benefit we promised? If you examine the output, you will notice that the records are displayed in sorted order (by account number)! This ordering is a simple consequence of the way we stored these records in the file, using direct-access techniques. Sorting with direct-access techniques is blazingly fast. The speed is achieved by making the file large enough to hold every possible record that might be created, which enables the program to insert a record between other records without having to reorganize the file. This configuration, of course, means that the file could be sparsely occupied most of the time, a waste of storage. So this situation is another example of the space/time trade-off. By using large amounts of space, we are able to develop a much faster sorting algorithm.

14.7.4. Case Study: A Transaction-Processing Program

We now present a substantial transaction-processing program (Fig. 14.33Fig. 14.36), using a random-access file to achieve instant-access processing. The program maintains a bank's account informationit displays existing accounts, updates accounts, adds new accounts and deletes accounts. We assume that the program in Fig. 14.24Fig. 14.25 has been executed to create a file, and that the program in Fig. 14.26Fig. 14.27 has been executed to insert initial data. The techniques used in this example were presented in the earlier RandomAccessFile examples.

The program has five options. Option 1 displays a list of all the accounts in the file, using the same techniques as in the preceding section. Choosing option 1 displays the information in Fig. 14.30.

Figure 14.30. Transaction processor displaying records.

Account First Name Last Name Balance 29 Nancy Brown -24.54 33 Stacey Dunn 314.33 37 Doug Barker 0.00 88 Dave Smith 258.34 96 Sam Stone 34.98  

Option 2 is used to update an account. The application will only update an existing record, so the function first checks to see whether the record specified by the user is empty. The record is read from the file, then the account number is compared to 0. If it is 0, the record contains no information, and a message is printed stating that the record is empty. Then, the menu choices are displayed. If the record contains information, the record's current information is displayed first. The user is then prompted for a change in the balance (either a charge or a payment), and the updated record is displayed. A typical output for option 2 is shown in Fig. 14.31.

Figure 14.31. Transaction processor updating a record.

(This item is displayed on page 719 in the print version)

Enter account to update ( 1 - 100 ): 37 37 Doug Barker 0.00 Enter charge ( + ) or payment ( - ): +87.99 37 Doug Barker 87.99  

Option 3 is used to add a new account to the file. The user is prompted to enter information for a new record. If the user enters an account number for an existing account, an error message is displayed indicating that the record already contains information, and the menu choices are printed again. If the account number entered does not correspond to an existing record (and all the data entered is valid), the new record is created and stored in the file. This code for this option uses the same process to add a new account as does the program in Fig. 14.26Fig. 14.27. A typical output for option 3 is shown in Fig. 14.32.

Figure 14.32. Transaction processor inserting a record.

Enter account number, first name, last name and balance. (Account number must be 1 - 100) ? 22 Sarah Johnston 247.45  

Option 4 is used to delete a record from the file. Deletion is accomplished by asking the user for the account number and reinitializing the record (i.e., writing a blank record in its place). If the account contains no information, deleteRecord displays an error message stating that the account does not exist. Option 5 terminates program execution. The program is shown in Fig. 14.33Fig. 14.36. Figure 14.33 defines the enum type for the user's options. The options are listed in lines 711.

Figure 14.33. Transaction processor's menu options.

1 // Fig. 14.33: MenuOption.java 2 // Defines an enum type for the credit inquiry program's options. 3 4 public enum MenuOption 5 { 6 // declare contents of enum type 7 PRINT( 1 ), 8 UPDATE( 2 ), 9 NEW( 3 ), 10 DELETE( 4 ), 11 END( 5 ); 12 13 private final int value; // current menu option 14 15 MenuOption( int valueOption ) 16 { 17 value = valueOption; 18 } // end MenuOptions enum constructor 19 20 public int getValue() 21 { 22 return value; 23 } // end method getValue 24 } // end enum MenuOption

Class FileEditor (Fig. 14.34) declares methods for manipulating records in a random-access file. This class uses all the techniques shown in the earlier examples. Method getrecord (lines 3145) reads the record with the given account number and stores its information in a RandomAccessAccountRecord object. Method updateRecord (lines 4864) modifies the record with the given account number, as long as the account number corresponds to a non-empty record. Method newRecord (lines 6783) adds a new record to the file using the provided account number, first name, last name and balance. Method deleteRecord (lines 86100) deletes the record with the given account number from the file, provided that the specified account exists. Method readRecords (lines 103136) displays all the currently existing records in the file.

Figure 14.34. FileEditor class that encapsulates the file-processing capabilities required in Fig. 14.35.

(This item is displayed on pages 720 - 722 in the print version)

1 // Fig. 14.34: FileEditor.java 2 // This class declares methods that manipulate bank account 3 // records in a random access file. 4 import java.io.EOFException; 5 import java.io.File; 6 import java.io.IOException; 7 import java.io.RandomAccessFile; 8 import java.util.Scanner; 9 10 import com.deitel.jhtp6.ch14.RandomAccessAccountRecord; 11 12 public class FileEditor 13 { 14 RandomAccessFile file; // reference to the file 15 Scanner input = new Scanner( System.in ); 16 17 // open the file 18 public FileEditor( String fileName ) throws IOException 19 { 20 file = new RandomAccessFile( fileName, "rw" ); 21 } // end FileEditor constructor 22 23 // close the file 24 public void closeFile() throws IOException 25 { 26 if ( file != null ) 27 file.close(); 28 } // end method closeFile 29 30 // get a record from the file 31 public RandomAccessAccountRecord getRecord( int accountNumber ) 32 throws IllegalArgumentException, NumberFormatException, IOException 33 { 34 RandomAccessAccountRecord record = new RandomAccessAccountRecord(); 35 36 if ( accountNumber < 1 || accountNumber > 100 ) 37 throw new IllegalArgumentException( "Out of range" ); 38 39 // seek appropriate record in file 40 file.seek( ( accountNumber - 1 ) * RandomAccessAccountRecord.SIZE ); 41 42 record.read( file ); 43 44 return record; 45 } // end method getRecord 46 47 // update record in file 48 public void updateRecord( int accountNumber, double transaction ) 49 throws IllegalArgumentException, IOException 50 { 51 RandomAccessAccountRecord record = getRecord( accountNumber ); 52 53 if ( record.getAccount() == 0 ) 54 throw new IllegalArgumentException( "Account does not exist" ); 55 56 // seek appropriate record in file 57 file.seek( ( accountNumber - 1 ) * RandomAccessAccountRecord.SIZE ); 58 59 record = new RandomAccessAccountRecord( 60 record.getAccount(), record.getFirstName(), 61 record.getLastName(), record.getBalance() + transaction ); 62 63 record.write( file ); // write updated record to file 64 } // end method updateRecord 65 66 // add record to file 67 public void newRecord( int accountNumber, String firstName, 68 String lastName, double balance ) 69 throws IllegalArgumentException, IOException 70 { 71 RandomAccessAccountRecord record = getRecord( accountNumber ); 72 73 if ( record.getAccount() != 0 ) 74 throw new IllegalArgumentException( "Account already exists" ); 75 76 // seek appropriate record in file 77 file.seek( ( accountNumber - 1 ) * RandomAccessAccountRecord.SIZE ); 78 79 record = new RandomAccessAccountRecord( accountNumber, 80 firstName, lastName, balance ); 81 82 record.write( file ); // write record to file 83 } // end method newRecord 84 85 // delete record from file 86 public void deleteRecord( int accountNumber ) 87 throws IllegalArgumentException, IOException 88 { 89 RandomAccessAccountRecord record = getRecord( accountNumber ); 90 91 if ( record.getAccount() == 0 ) 92 throw new IllegalArgumentException( "Account does not exist" ); 93 94 // seek appropriate record in file 95 file.seek( ( accountNumber - 1 ) * RandomAccessAccountRecord.SIZE ); 96 97 // create a blank record to write to the file 98 record = new RandomAccessAccountRecord(); 99 record.write( file ); 100 } // end method deleteRecord 101 102 // read and display records 103 public void readRecords() 104 { 105 RandomAccessAccountRecord record = new RandomAccessAccountRecord(); 106 107 System.out.printf( "%-10s%-15s%-15s%10s ", "Account", 108 "First Name", "Last Name", "Balance" ); 109 110 try // read a record and display 111 { 112 file.seek( 0 ); 113 114 while ( true ) 115 { 116 do 117 { 118 record.read( file ); 119 } while ( record.getAccount() == 0 ); 120 121 // display record contents 122 System.out.printf( "%-10d%-15s%-15s%10.2f ", 123 record.getAccount(), record.getFirstName(), 124 record.getLastName(), record.getBalance() ); 125 } // end while 126 } // end try 127 catch ( EOFException eofException ) // close file 128 { 129 return; // end of file was reached 130 } // end catch 131 catch ( IOException ioException ) 132 { 133 System.err.println( "Error reading file." ); 134 System.exit( 1 ); 135 } // end catch 136 } // end method readRecords 137 } // end class FileEditor

Class transactionProcessor (Fig. 14.35) displays the menu for the application and manages the interactions with the FileEditor object that is created in the openFile method (lines 2034).

Figure 14.35. Transaction-processing program.

(This item is displayed on pages 723 - 726 in the print version)

1 // Fig. 14.35: TransactionProcessor.java 2 // A transaction processing program using random-access files. 3 import java.io.IOException; 4 import java.util.NoSuchElementException; 5 import java.util.Scanner; 6 7 import com.deitel.jhtp6.ch14.RandomAccessAccountRecord; 8 9 public class TransactionProcessor 10 { 11 private FileEditor dataFile; 12 private RandomAccessAccountRecord record; 13 private MenuOption choices[] = { MenuOption.PRINT, 14 MenuOption.UPDATE, MenuOption.NEW, 15 MenuOption.DELETE, MenuOption.END }; 16 17 private Scanner input = new Scanner( System.in ); 18 19 // get the file name and open the file 20 private boolean openFile() 21 { 22 try // attempt to open file 23 { 24 // call the helper method to open the file 25 dataFile = new FileEditor( "clients.dat" ); 26 } // end try 27 catch ( IOException ioException ) 28 { 29 System.err.println( "Error opening file." ); 30 return false; 31 } // end catch 32 33 return true; 34 } // end method openFile 35 36 // close file and terminate application 37 private void closeFile() 38 { 39 try // close file 40 { 41 dataFile.closeFile(); 42 } // end try 43 catch ( IOException ioException ) 44 { 45 System.err.println( "Error closing file." ); 46 System.exit( 1 ); 47 } // end catch 48 } // end method closeFile 49 50 // create, update or delete the record 51 private void performAction( MenuOption action ) 52 { 53 int accountNumber = 0; // account number of record 54 String firstName; // first name for account 55 String lastName; // last name for account 56 double balance; // account balance 57 double transaction; // amount to change in balance 58 59 try // attempt to manipulate files based on option selected 60 { 61 switch ( action ) // switch based on option selected 62 { 63 case PRINT: 64 System.out.println(); 65 dataFile.readRecords(); 66 break; 67 case NEW: 68 System.out.printf( " %s%s %s %s", 69 "Enter account number,", 70 " first name, last name and balance.", 71 "(Account number must be 1 - 100)", "? " ); 72 73 accountNumber = input.nextInt(); // read account number 74 firstName = input.next(); // read first name 75 lastName = input.next(); // read last name 76 balance = input.nextDouble(); // read balance 77 78 dataFile.newRecord( accountNumber, firstName, 79 lastName, balance ); // create new record 80 break; 81 case UPDATE: 82 System.out.print( 83 " Enter account to update ( 1 - 100 ): " ); 84 accountNumber = input.nextInt(); 85 record = dataFile.getRecord( accountNumber ); 86 87 if ( record.getAccount() == 0 ) 88 System.out.println( "Account does not exist." ); 89 else 90 { 91 // display record contents 92 System.out.printf( "%-10d%-12s%-12s%10.2f ", 93 record.getAccount(), record.getFirstName(), 94 record.getLastName(), record.getBalance() ); 95 96 System.out.print( 97 "Enter charge ( + ) or payment ( - ): " ); 98 transaction = input.nextDouble(); 99 dataFile.updateRecord( accountNumber, // update record 100 transaction ); 101 102 // retrieve updated record 103 record = dataFile.getRecord( accountNumber ); 104 105 // display updated record 106 System.out.printf( "%-10d%-12s%-12s%10.2f ", 107 record.getAccount(), record.getFirstName(), 108 record.getLastName(), record.getBalance() ); 109 } // end else 110 break; 111 case DELETE: 112 System.out.print( 113 " Enter an account to delete (1 - 100): " ); 114 accountNumber = input.nextInt(); 115 116 dataFile.deleteRecord( accountNumber ); // delete record 117 break; 118 default: 119 System.out.println( "Invalid action." ); 120 break; 121 } // end switch 122 } // end try 123 catch ( NumberFormatException format ) 124 { 125 System.err.println( "Bad input." ); 126 } // end catch 127 catch ( IllegalArgumentException badAccount ) 128 { 129 System.err.println( badAccount.getMessage() ); 130 } // end catch 131 catch ( IOException ioException ) 132 { 133 System.err.println( "Error writing to the file." ); 134 } // end catch 135 catch ( NoSuchElementException elementException ) 136 { 137 System.err.println( "Invalid input. Please try again." ); 138 input.nextLine(); // discard input so user can try again 139 } // end catch 140 } // end method performAction 141 142 // enable user to input menu choice 143 private MenuOption enterChoice() 144 { 145 int menuChoice = 1; 146 147 // display available options 148 System.out.printf( " %s %s %s %s %s %s", 149 "Enter your choice", "1 - List accounts", 150 "2 - Update an account", "3 - Add a new account", 151 "4 - Delete an account", "5 - End program ? " ); 152 153 try 154 { 155 menuChoice = input.nextInt(); 156 } 157 catch ( NoSuchElementException elementException ) 158 { 159 System.err.println( "Invalid input." ); 160 System.exit( 1 ); 161 } // end catch 162 163 return choices[ menuChoice - 1 ]; // return choice from user 164 } // end enterChoice 165 166 public void processRequests() 167 { 168 openFile(); 169 170 // get user's request 171 MenuOption choice = enterChoice(); 172 173 while ( choice != MenuOption.END ) 174 { 175 performAction (choice ); 176 choice = enterChoice(); 177 } // end while 178 179 closeFile(); 180 } // end method processRequests 181 } // end class TransactionProcessor

Figure 14.36. Testing class transactionProcessor.

(This item is displayed on page 726 in the print version)

1 // Fig. 14.36: TransactionProcessorTest.java 2 // Testing the transaction processor. 3 4 public class TransactionProcessorTest 5 { 6 public static void main( String args[] ) 7 { 8 TransactionProcessor application = new TransactionProcessor(); 9 application.processRequests(); 10 } // end main 11 } // end class TransactionProcessorTest

Method processRequests (lines 166180) processes the choices entered by the user. If the user does not enter 5 (which ends the program), method performAction (lines 51140) is called. This method inputs information from the user and sends it to the appropriate method of class FileEditor (Fig. 14.34), which encapsulates the file-processing operations in this example. The method to call is determined by performAction's MenuOption argument. Each option is handled in the switch statement of lines 61121. Method performAction also handles any exceptions that might be thrown from FileEditor's methods.

Категории