Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

AhmadLang / Java, How To Program, 2004

.pdf
Скачиваний:
630
Добавлен:
31.05.2015
Размер:
51.82 Mб
Скачать

be

300 Pam Worthington 0.00

The new record is larger (has more characters) than the original record. The characters beyond the second "o" in "Worthington" would overwrite the beginning of the next sequential record in the file. The problem here is that fields in a text fileand hence recordscan vary in size. For example, 7, 14,117, 2074 and 27383 are all ints stored in the same number of bytes (4) internally, but they are different-sized fields when displayed on the screen or written to a file as text.

Therefore, records in a sequential-access file are not usually updated in place. Instead, the entire file is usually rewritten. To make the preceding name change, the records before 300 Pam White 0.00 in a sequential-access file would be copied to a new file, the new record (which can be of a different size than the one it replaces) would be written and the records after 300 Pam White 0.00 would be copied to the new file. It is uneconomical just to update one record, but reasonable if a substantial portion of the records needs to be updated.

[Page 697 (continued)]

14.6. Object Serialization

In Section 14.5, we demonstrated how to write the individual fields of an AccountRecord object into a file as text, and how to read those fields from a file and place their values into an AccountRecord object in memory. In the examples, AccountRecord was used to aggregate the information for one record. When the instance variables for an AccountRecord were output to a disk file, certain information was lost, such as the type of each value. For instance, if the value "3" were read from a file, there is no way to tell if the value came from an int, a String or a double. We have only data, not type information, on a disk. If the program that is going to read this data "knows" what object type the data corresponds to, then the data is simply read into objects of that type. For example, in Section 14.5.2, we know that we are inputting an int (the account number), followed by two Strings (the first and last name) and a double (the balance). We also know that these values are separated by spaces, with only one record on each line. Sometimes we will not know exactly how the data is stored in a file. In such cases, we would like to read or write an entire object from a file. Java provides such a mechanism, called object serialization. A so-called serialized object is an object represented as a sequence of bytes that includes the object's data as well as information about the object's type and the types of data stored in the object. After a serialized object has been written into a file, it can be read from the file and deserializedthat is, the type information and bytes that represent the object and its data can be used to recreate the object in memory.

[Page 698]

Classes ObjectInputStream and ObjectOutputStream, which respectively implement the ObjectInput and

ObjectOutput interfaces, enable entire objects to be read from or written to a stream (possibly a file). To use serialization with files, we initialize ObjectInputStream and ObjectOutputStream objects with stream objects that read from and write to filesobjects of classes FileInputStream and FileOutputStream, respectively. Initializing stream objects with other stream objects in this manner is sometimes called wrappingthe new stream object being created wraps the stream object specified as a constructor argument. To wrap a FileInputStream in an ObjectInputStream, for instance, we pass the FileInputStream object to the ObjectInputStream's constructor.

The ObjectOutput interface contains method writeObject, which takes an Object that implements interface Serializable (discussed shortly) as an argument and writes its information to an OutputStream. Correspondingly, the ObjectInput interface contains method readObject, which reads and returns a reference to an Object from an InputStream. After an object has been read, its reference can be cast to the object's actual type. As you will see in Chapter 24, Networking, applications that communicate via a network, such as the Internet, can also transmit entire objects across the network.

In this section, we create and manipulate sequential-access files using object serialization. Object serialization is performed with byte-based streams, so the sequential files created and manipulated will be binary files. Recall that binary files cannot be viewed in standard text editors. For this reason, we write a separate application that knows how to read and display serialized objects.

14.6.1. Creating a Sequential-Access File Using Object Serialization

We begin by creating and writing serialized objects to a sequential-access file. In this section, we reuse much of the code from Section 14.5, so we focus only on the new features.

Defining the AccountRecordSerializable Class

Let us begin by modifying our AccountRecord class so that objects of this class can be serialized. Class AccountRecordSerializable (Fig. 14.17) implements interface Serializable (line 7), which allows objects of

AccountRecordSerializable to be serialized and deserialized with ObjectOutputStreams and ObjectInputStreams. Interface Serializable is a tagging interface. Such an interface contains no methods. A class that implements Serializable is tagged as being a Serializable object. This is important because an ObjectOutputStream will not output an object unless it is a Serializable object, which is the case for any object of a class that implements

Serializable.

[Page 700]

Figure 14.17. AccountRecordSerializable class for serializable objects.

 

 

(This item is displayed on pages 699 - 700 in the print version)

1

// Fig. 14.17: AccountRecordSerializable.java

2

// A class that represents one record of information.

3

package

com.deitel.jhtp6.ch14; // packaged for reuse

4

 

 

5

import

java.io.Serializable;

6

 

 

7

public

class AccountRecordSerializable implements Serializable

8{

9private int account;

10private String firstName;

11private String lastName;

12private double balance;

14// no-argument constructor calls other constructor with default values

15public AccountRecordSerializable()

16{

17this ( 0, "", "", 0.0 );

18} // end no-argument AccountRecordSerializable constructor

20// four-argument constructor initializes a record

21public AccountRecordSerializable(

22int acct, String first, String last, double bal )

23{

24setAccount( acct );

25setFirstName( first );

26setLastName( last );

27setBalance( bal );

28} // end four-argument AccountRecordSerializable constructor

30// set account number

31public void setAccount( int acct )

32{

33account = acct;

34} // end method setAccount

36// get account number

37public int getAccount()

38{

39return account;

40} // end method getAccount

42// set first name

43public void setFirstName( String first )

44{

45firstName = first;

46} // end method setFirstName

48// get first name

49public String getFirstName()

50{

51return firstName;

52} // end method getFirstName

54// set last name

55public void setLastName( String last )

56{

57lastName = last;

58} // end method setLastName

60// get last name

61public String getLastName()

62{

63return lastName;

64} // end method getLastName

66// set balance

67public void setBalance( double bal )

68{

69balance = bal;

70} // end method setBalance

72// get balance

73public double getBalance()

74{

75return balance;

76} // end method getBalance

77} // end class AccountRecordSerializable

In a class that implements Serializable, the programmer must ensure that every instance variable of the class is a Serializable type. Any instance variable that is not serializable must be declared transient to indicate that it is not Serializable and should be ignored during the serialization process. By default, all primitive-type variables are serializable. For variables of reference types, you must check the definition of the class (and possibly its superclasses) to ensure that the type is Serializable. By default, array objects are serializable. However, if the array contains references to other objects, those objects may or may not be serializable.

Class AccountRecordSerializable contains private data members account, firstName, lastName and balance. This class also provides public get and set methods for accessing the private fields.

Now let us discuss the code that creates the sequential-access file (Fig. 14.18Fig. 14.19). We concentrate only on new

concepts here. As stated in Section 14.3, a program can open a file by creating an object of stream class FileInputStream or FileOutputStream. In this example, the file is to be opened for output, so the program creates a FileOutputStream (line 21 of Fig. 14.18). The string argument that is passed to the FileOutputStream's constructor represents the name and path of the file to be opened. Existing files that are opened for output in this manner are truncated. Note that the .ser file extension is usedwe use this file extension for binary files that contain serialized objects.

[Page 701]

Figure 14.18. Sequential file created using ObjectOutputStream.

(This item is displayed on pages 701 - 702 in the print version)

1 // Fig. 14.18: CreateSequentialFile.java

2 // Writing objects sequentially to a file with class ObjectOutputStream.

3import java.io.FileOutputStream;

4import java.io.IOException;

5import java.io.ObjectOutputStream;

6import java.util.NoSuchElementException;

7import java.util.Scanner;

8

9 import com.deitel.jhtp6.ch14.AccountRecordSerializable; 10

11public class CreateSequentialFile

12{

13private ObjectOutputStream output; // outputs data to file

15// allow user to specify file name

16public void openFile()

17{

18try // open file

19{

20output = new ObjectOutputStream(

21new FileOutputStream( "clients.ser" ) );

22} // end try

23catch ( IOException ioException )

24{

25System.err.println( "Error opening file." );

26} // end catch

27} // end method openFile

28

29// add records to file

30public void addRecords()

31{

32AccountRecordSerializable record; // object to be written to file

33int accountNumber = 0; // account number for record object

34String firstName; // first name for record object

35String lastName; // last name for record object

36double balance; // balance for record object

37

38 Scanner input = new Scanner( System.in ); 39

40System.out.printf( "%s\n%s\n%s\n%s\n\n",

41"To terminate input, type the end-of-file indicator ",

42"when you are prompted to enter input.",

43"On UNIX/Linux/Mac OS X type <ctrl> d then press Enter",

44"On Windows type <ctrl> z then press Enter" );

45

46System.out.printf( "%s\n%s",

47"Enter account number (> 0), first name, last name and balance.",

48"? " );

49

50while ( input.hasNext() ) // loop until end-of-file indicator

51{

52try // output values to file

53{

54accountNumber = input.nextInt(); // read account number

55firstName = input.next(); // read first name

56lastName = input.next(); // read last name

57balance = input.nextDouble(); // read balance

58

59if ( accountNumber > 0 )

60{

61

// create new

record

 

 

62

record = new

AccountRecordSerializable(

accountNumber,

63

firstName,

lastName, balance

);

 

64

output.writeObject( record ); //

output

record

65} // end if

66else

67{

68

System.out.println(

69

"Account number must be greater than 0." );

70} // end else

71} // end try

72catch ( IOException ioException )

73{

74System.err.println( "Error writing to file." );

75return;

76} // end catch

77catch ( NoSuchElementException elementException )

78{

79System.err.println( "Invalid input. Please try again." );

80input.nextLine(); // discard input so user can try again

81} // end catch

82

83System.out.printf( "%s %s\n%s", "Enter account number (>0),",

84"first name, last name and balance.", "? " );

85} // end while

86} // end method addRecords

87

88// close file and terminate application

89public void closeFile()

90{

91try // close file

92{

93if ( output != null )

94output.close();

95} // end try

96catch ( IOException ioException )

97{

98System.err.println( "Error closing file.");

99System.exit( 1 );

100} // end catch

101} // end method closeFile

102} // end class CreateSequentialFile

Figure 14.19. Testing class CreateSequentialFile.

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

1// Fig. 14.19: CreateSequentialFileTest.java

2// Testing class CreateSequentialFile.

3

4public class CreateSequentialFileTest

5{

6public static void main( String args[] )

7{

8CreateSequentialFile application = new CreateSequentialFile();

10application.openFile();

11application.addRecords();

12application.closeFile();

13} // end main

14} // end class CreateSequentialFileTest

To terminate input, type the end-of-file indicator when you are prompted to enter input.

On UNIX/Linux/Mac OS X type <ctrl> d then press Enter On Windows type <ctrl> z then press Enter

Enter

account number (> 0), first name, last name and balance.

? 100

Bob Jones 24.98

Enter

account number (> 0), first name, last name and balance.

? 200

Steve Doe -345

.67

Enter

account number (> 0), first name, last name and balance.

? 300

Pam White 0.00

 

Enter

account number (> 0), first name, last name and balance.

? 400

Sam Stone -42.16

Enter

account number (> 0), first name, last name and balance.

? 500

Sue Rich 224.62

Enter

account number

(> 0), first name, last name and balance.

? ^Z

 

 

[Page 703]

Common Programming Error 14.2

It is a logic error to open an existing file for output when, in fact, the user wishes to preserve the file.

Class FileOutputStream provides methods for writing byte arrays and individual bytes to a file. In this program we wish to write objects to a filea capability not provided by FileOutputStream. For this reason, we wrap a FileOutputStream in an ObjectOutputStream by passing the new FileOutputStream object to the ObjectOutputStream's constructor (lines 2021). The ObjectOutputStream object uses the FileOutputStream object to write objects into the file. Lines 2021 might throw an IOException if a problem occurs while opening the file (e.g., when a file is opened for writing on a drive with insufficient space or when a read-only file is opened for writing). If so, the program displays an error message (lines 2326). If no exception occurs, the file is open and variable output can be used to write objects to the file.

This program assumes that data is input correctly and in the proper record-number order. Method addRecords (lines 3086) performs the write operation. Lines 6263 create an AccountRecordSerializable object from the data entered by the user. Line 64 calls ObjectOutputStream method writeObject to write the record object to the output file. Note that only one statement is required to write the entire object.

[Page 704]

Method closeFile (lines 89101) closes the file. Method closeFile calls ObjectOutputStream method close on output to close both the ObjectOutputStream and its underlying FileOutputStream (line 94). Note that the call to method close is contained in a try block. Method close throws an IOException if the file cannot be closed properly. In this case, it is important to notify the user that the information in the file might be corrupted. When using wrapped streams, closing the outermost stream also closes the underlying file.

In the sample execution for the program in Fig. 14.19, we entered information for five accountsthe same information shown in Fig. 14.10. The program does not show how the data records actually appear in the file. Remember that now we are using binary files, which are not humanly readable. To verify that the file has been created successfully, the next section presents a program to read the file's contents.

14.6.2. Reading and Deserializing Data from a Sequential-Access File

As discussed in Section 14.5.2, data is stored in files so that it may be retrieved for processing when needed. The preceding section showed how to create a file for sequential access using object serialization. In this section, we discuss how to read serialized data sequentially from a file.

The program in Fig. 14.20Fig. 14.21 reads records from a file created by the program in Section 14.6.1 and displays the contents. The program opens the file for input by creating a FileInputStream object (line 21). The name of the file to open is specified as an argument to the FileInputStream constructor. In Fig. 14.18, we wrote objects to the file, using an ObjectOutputStream object. Data must be read from the file in the same format in which it was written. Therefore, we use an ObjectInputStream wrapped around a FileInputStream in this program (lines 2021) If no exceptions occur when opening the file, variable input can be used to read objects from the file.

[Page 706]

Figure 14.20. Sequential file read using an ObjectInputStream.

(This item is displayed on pages 704 - 705 in the print version)

1 // Fig. 14.20: ReadSequentialFile.java

2 // This program reads a file of objects sequentially

3// and displays each record.

4import java.io.EOFException;

5import java.io.FileInputStream;

6import java.io.IOException;

7import java.io.ObjectInputStream;

9import com.deitel.jhtp6.ch14.AccountRecordSerializable;

10

11public class ReadSequentialFile

12{

13private ObjectInputStream input;

15// enable user to select file to open

16public void openFile()

17{

18try // open file

19{

20input = new ObjectInputStream(

21new FileInputStream( "clients.ser" ) );

22} // end try

23catch ( IOException ioException )

24{

25System.err.println( "Error opening file." );

26} // end catch

27} // end method openFile

28

29// read record from file

30public void readRecords()

31{

32AccountRecordSerializable record;

33System.out.printf( "%-10s%-12s%-12s%10s\n", "Account",

34"First Name", "Last Name", "Balance" );

35

36try // input the values from the file

37{

38while ( true )

39{

40record = ( AccountRecordSerializable ) input.readObject();

42// display record contents

43System.out.printf( "%-10d%-12s%-12s%10.2f\n",

44

record.getAccount(), record.getFirstName(),

45

record.getLastName(), record.getBalance() );

46} // end while

47} // end try

48catch ( EOFException endOfFileException )

49{

50return; // end of file was reached

51} // end catch

52catch ( ClassNotFoundException classNotFoundException )

53{

54System.err.println( "Unable to create object." );

55} // end catch

56catch ( IOException ioException )

57{

58System.err.println( "Error during read from file." );

59} // end catch

60} // end method readRecords

61

62// close file and terminate application

63public void closeFile()

64{

65try // close file and exit

66{

67if ( input != null )

68input.close();

69} // end try

70catch ( IOException ioException )

71{

72System.err.println( "Error closing file." );

73System.exit( 1 );

74} // end catch

75} // end method closeFile

76} // end class ReadSequentialFile

Figure 14.21. Testing class ReadSequentialFile.

1// Fig. 14.21: ReadSequentialFileTest.java

2// This program test class ReadSequentialFile.

4public class ReadSequentialFileTest

5{

6public static void main( String args[] )

7{

8ReadSequentialFile application = new ReadSequentialFile();

10application.openFile();

11application.readRecords();

12application.closeFile();

13} // end main

14} // end class ReadSequentialFileTest

Account

First Name

Last Name

Balance

100

Bob

Jones

24.98

200

Steve

Doe

-345

.67

300

Pam

White

0

.00

400

Sam

Stone

-42

.16

500

Sue

Rich

224

.62

 

 

 

 

 

The program reads records from the file in method readRecords (lines 3060). Line 40 calls ObjectInputStream method readObject to read an Object from the file. To use AccountRecordSerializable-specific methods, we downcast the returned Object to type AccountRecordSerializable. Method readObject tHRows an EOFException (processed at lines 4851) if an attempt is made to read beyond the end of the file. Method readObject throws a ClassNotFoundException if the class for the object being read cannot be located. This might occur if the file is accessed on a computer that does not have the class. Figure 14.21 contains method main (lines 613), which opens the file, calls method readRecords and closes the file.

[Page 706 (continued)]

14.7. 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. Randomaccess files are sometimes called direct-access files.

[Page 707]

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 randomaccess 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.

[View full size image]

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 randomaccess 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.

[Page 708]

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.

[Page 709]

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

 

 

 

 

 

 

5import java.io.RandomAccessFile;

6import java.io.IOException;

7

8public class RandomAccessAccountRecord extends AccountRecord

9{

10public static final int SIZE = 72;

11

12// no-argument constructor calls other constructor with default values

13public RandomAccessAccountRecord()

14{

15this ( 0, "", "", 0.0 );

16} // end no-argument RandomAccessAccountRecord constructor

17

18// initialize a RandomAccessAccountRecord

19public RandomAccessAccountRecord( int account, String firstName,

20String lastName, double balance )

21{

22super( account, firstName, lastName, balance );

23} // end four-argument RandomAccessAccountRecord constructor

24

25// read a record from specified RandomAccessFile

26public void read( RandomAccessFile file ) throws IOException

27{

28setAccount( file.readInt() );

29setFirstName( readName( file ) );

30setLastName( readName( file ) );

31setBalance( file.readDouble() );

32} // end method read

33