Received: from PACIFIC-CARRIER-ANNEX.MIT.EDU by po10 (5.61/4.7) id AA09325; Tue, 9 May 00 16:11:28 EDT Received: from hermes.javasoft.com by MIT.EDU with SMTP id AA26578; Tue, 9 May 00 16:15:27 EDT Received: (from nobody@localhost) by hermes.java.sun.com (8.9.3+Sun/8.9.1) id UAA09688; Tue, 9 May 2000 20:14:37 GMT Date: Tue, 9 May 2000 20:14:37 GMT Message-Id: <200005092014.UAA09688@hermes.java.sun.com> X-Authentication-Warning: hermes.java.sun.com: Processed from queue /bulkmail/data/ed_97/mqueue2 X-Mailing: 203 From: JDCTechTips@sun.com Subject: JDC Tech Tips May 9, 2000 To: JDCMember@sun.com Reply-To: JDCTechTips@sun.com Errors-To: bounced_mail@hermes.java.sun.com Precedence: junk Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii X-Mailer: Beyond Email 2.2 J D C T E C H T I P S TIPS, TECHNIQUES, AND SAMPLE CODE WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips, May 9, 2000. This issue covers: * Random Access for Files * Using Adapters These tips were developed using Java(tm) 2 SDK, Standard Edition, v 1.2.2. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - RANDOM ACCESS FOR FILES If you've used the standard Java package java.io, you're probably familiar with I/O classes such as InputStream and BufferedReader. These classes support sequential I/O on files. That is, you read a file starting at the beginning, or write a file from its beginning or by appending to it. The class java.io.RandomAccessFile operates a little differently. It supports random access, that is, access where you can set a file pointer to an arbitrary offset (represented as a 64-bit long value), and then perform I/O from that position. Unlike classes such as FileInputStream, RandomAccessFile is not part of the InputStream/OutputStream hierarchy; you can't say: InputStream istr = new RandomAccessFile(...); RandomAccessFile identifies file locations according to byte offsets. In the Java(tm) language, bytes and characters are distinct. A character is made up of two bytes, so a particular byte offset in a file of characters doesn't necessarily represent a location of a character. The RandomAccessFile class implements the DataInput and DataOutput interfaces. These support methods like readInt and writeUTF to read and write standard data types in a uniform way. For example, if you use writeInt to write an integer to a file, you can then use readInt to read the integer back from the file, with byte-ordering issues automatically handled for you. To see how RandomAccessFile might be useful, consider an application with a large legacy database of fixed-length records. These records are accessed in random order. For example, the records might represent a hash table on disk, or some type of complex linked data structure. The application needs to read a specific record in the database without having to read all the records before it. Here is an illustration of this application using two programs, the first is a C program that writes a database. The database represents the legacy part of the application. The format of records in the database is: number of records as a two-byte short name of a person, up to 25 bytes, unused bytes 0 filled birthdate month as a two-byte short birthdate day as a two-byte short birthdate year as a two-byte short ... The C program looks like this: #include #include /* structure of a record */ struct Rec { char* name; short month; short day; short year; }; /* birthdate/name record */ struct Rec data[] = { {"Jane Jones", 3, 17, 1959}, {"Bill Smith", 2, 27, 1947}, {"Maria Thomas", 12, 23, 1954}, {"Mortimer Smedley Williams", 9, 24, 1957}, {"Jennifer Garcia Throckmorton", 11, 9, 1963} }; short NUMVALUES = sizeof(data) / sizeof(struct Rec); /* write a short to a file */ void writeShort(FILE* fp, short s) { fputc((s >> 8) & 0xff, fp); fputc(s & 0xff, fp); } /* write a sequence of bytes to a file */ void writeBytes(FILE* fp, char* buf, size_t n) { fwrite(buf, 1, n, fp); } int main() { int i; /* open the output file */ FILE* fpout = fopen("out.data", "wb"); if (fpout == NULL) { fprintf(stderr, "Cannot open output file\n"); return 1; } /* write out the number of values */ writeShort(fpout, NUMVALUES); /* write the data to the file */ for (i = 0; i < NUMVALUES; i++) { struct Rec* p = &data[i]; char outbuf[25]; /* write the name, truncating if necessary */ strncpy(outbuf, p->name, sizeof outbuf); writeBytes(fpout, outbuf, sizeof outbuf); /* write month/day/year */ writeShort(fpout, p->month); writeShort(fpout, p->day); writeShort(fpout, p->year); } fclose(fpout); return 0; } You need to be careful about byte ordering when you write data to a file for later reading by another application. For example, when the above program writes out short values to the file, it must ensure that the two bytes of the short are written in the order that RandomAccessFile.readShort expects them -- high byte first, then low byte. You can't use readShort to read a legacy database that has values whose bytes are reversed -- low byte first, then high byte. You'd need to read raw bytes and assemble the short value yourself. The Java program that reads the file looks like this: import java.io.RandomAccessFile; import java.io.IOException; public class RAFDemo { // starting offset in data file, past the count of records static final int STARTING_OFFSET = 2; // length of a name static final int NAME_LENGTH = 25; // bytes in a record (name length + three shorts) static final int BYTES_IN_RECORD = NAME_LENGTH + 2 + 2 + 2; public static void main(String args[]) throws IOException { RandomAccessFile raf = new RandomAccessFile("out.data", "r"); // read the number of data records short numvalues = raf.readShort(); byte namebuf[] = new byte[NAME_LENGTH]; // read each record, going backwards through the file for (int i = numvalues - 1; i >= 0; i--) { // seek to the record raf.seek(STARTING_OFFSET + i * BYTES_IN_RECORD); // read the name as a vector of bytes raf.read(namebuf); // convert the name to a string StringBuffer namesb = new StringBuffer(); for (int j = 0; j < NAME_LENGTH; j++) { if (namebuf[j] == 0) { break; } else { namesb.append((char)namebuf[j]); } } // read month/day/year short month = raf.readShort(); short day = raf.readShort(); short year = raf.readShort(); // display the results System.out.println(namesb.toString() + " " + month + " " + day + " " + year); } } } To demonstrate that it can access parts of the database at random, the program reads the file records in backwards order and then displays the records. If you run the program, output is: Jennifer Garcia Throckmor 11 9 1963 Mortimer Smedley Williams 9 24 1957 Maria Thomas 12 23 1954 Bill Smith 2 27 1947 Jane Jones 3 17 1959 The first name has been truncated to 25 characters, to fit the requirements of record layout stated above. One issue related to RandomAccessFile concerns strings. The RAFDemo example just presented reads a legacy database. Strings in a database record, such as "Jane Jones", are represented in RAFDemo as a sequence of bytes. To convert the bytes to a string, the program converts each byte to a character and then appends it to a StringBuffer. Suppose, however, that you want to use RandomAccessFile with full 16-bit Unicode characters. How can you do this? One way is to use the readUTF and writeUTF methods found in DataInput and DataOutput. These represent strings as UTF sequences; 7-bit ASCII characters are represented as themselves, and other characters as two or three bytes. The methods read or write two bytes of length information, followed by the UTF representation as a stream of bytes. Because the length is stored as two bytes, you cannot write extremely long strings this way. Another approach to writing strings is to use the writeChars method. This method writes a sequence of characters (there's no readChars method). If you use writeChars, you need to write out the string length first using writeInt. If you use RandomAccessFile to access fixed-length records containing strings, you need to determine how you're going to represent the records. The complication is that strings are typically variable in length. You need to either truncate strings, so that all are a fixed length (as in the example above), or represent the strings in a separate file and record string numbers in the actual records. For example, you might use an integer field in a fixed-length record to store a value "37", where 37 represents the 37th string in a separate file. For further information about java.io.RandomAccessFile, see "The Java(tm) Language Specification" section 22.23. Also see "Java 2(tm) Platform, Standard Edition, v 1.2.2 API: Specification: Class RandomAccessFile" (http://java.sun.com/products//jdk/1.2/docs/api/java/io/RandomAccessFile.html). - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - USING ADAPTERS Suppose that you're doing some programming in the Java language, and you have a class that implements an interface. Let's call the interface A: interface A { public void f1(); public void f2(); public void f3(); public void f4(); public void f5(); } The interface has five methods, but you're only interested in the first one, f1. So you say: class B implements A { public void f1() {/*...*/} } Unfortunately, this usage is invalid, and results in a compile error. For a class to implement an interface, it must define all the interface's methods. If you make B abstract, the error goes away, but you can't create objects of an abstract class. If you extend B to a non-abstract class C, you're presented with the error again. To deal with this problem, use "adapter" classes. An adapter class for A looks like this: abstract class A_ADAPTER implements A { public void f1() {} public void f2() {} public void f3() {} public void f4() {} public void f5() {} } Notice that the adapter class implements all the methods of the interface as dummy methods that do nothing. If you want to extend f1 to provide your own functionality, you then say: class B extends A_ADAPTER { public void f1() {/*...*/} } In this case, a user of class B gets the overriding version of the f1 method, and the dummy version of the f2-f5 methods found in the abstract class A_ADAPTER. One place where adapters are used is AWT event handling. The WindowListener interface specifies seven events that relate to window handling, and WindowAdapter defines dummy methods for these events: public void windowOpened(WindowEvent e) {} public void windowClosing(WindowEvent e) {} public void windowClosed(WindowEvent e) {} public void windowIconified(WindowEvent e) {} public void windowDeiconified(WindowEvent e) {} public void windowActivated(WindowEvent e) {} public void windowDeactivated(WindowEvent e) {} WindowAdapter provides dummy implementations of all the methods that are specified by WindowListener. So if you extend the WindowAdapter class, you only need to provide implementations of methods whose default functionality you want to override. Here's an example that extends WindowAdapter: import java.awt.event.*; import javax.swing.*; public class AdapterDemo { public static void main(String args[]) { // set up a frame JFrame frame = new JFrame("AdapterDemo"); // set up listeners for window iconification // and for window closing frame.addWindowListener(new WindowAdapter() { public void windowIconified(WindowEvent e) { System.out.println(e); } public void windowClosing(WindowEvent e) { System.exit(0); } }); // set up panels and labels JPanel panel = new JPanel(); JLabel label = new JLabel("This is a test"); panel.add(label); // position and display frame frame.getContentPane().add(panel); frame.setSize(300, 200); frame.setLocation(200, 200); frame.setVisible(true); } } This particular example sets up listeners for the WindowClosing and WindowIconified (minimized) events, and ignores the others. Notation like: frame.addWindowListener(new WindowAdapter() {...}); defines an anonymous inner class that extends WindowAdapter. Similar adapter classes are used for keyboard and mouse event handling. For further information about adapters, see the section "AWT Adapters" in Chapter 9 of the book "Graphic Java - Mastering the JFC, 3rd Edition, Volume 1, AWT", by David Geary. Also see "Java 2(tm) Platform, Standard Edition, v 1.2.2 API: Specification: Class WindowAdapter" (http://java.sun.com/products//jdk/1.2/docs/api/java/awt/event/WindowAdapter.html). . . . . . . . . . . . . . . . . . . . . . . . - NOTE The names on the JDC mailing list are used for internal Sun Microsystems(tm) purposes only. To remove your name from the list, see Subscribe/Unsubscribe below. - FEEDBACK Comments? Send your feedback on the JDC Tech Tips to: jdc-webmaster@sun.com - SUBSCRIBE/UNSUBSCRIBE The JDC Tech Tips are sent to you because you elected to subscribe when you registered as a JDC member. To unsubscribe from JDC email, go to the following address and enter the email address you wish to remove from the mailing list: http://developer.java.sun.com/unsubscribe.html To become a JDC member and subscribe to this newsletter go to: http://java.sun.com/jdc/ - ARCHIVES You'll find the JDC Tech Tips archives at: http://developer.java.sun.com/developer/TechTips/index.html - COPYRIGHT Copyright 2000 Sun Microsystems, Inc. All rights reserved. 901 San Antonio Road, Palo Alto, California 94303 USA. This document is protected by copyright. For more information, see: http://developer.java.sun.com/developer/copyright.html This issue of the JDC Tech Tips is written by Glen McCluskey. JDC Tech Tips May 9, 2000