[ Team LiB ] Previous Section Next Section

3.9 Random Access to Files

The examples we've seen so far have all read or written file content using streams. Streams provide sequential access to data and are particularly useful for network applications, which are often stream-oriented. Files stored on modern hard disks (as opposed to streaming tape drives) need not be accessed sequentially, and Java provides random access to files with the RandomAccessFile class. Example 3-8 demonstrates the use of random-access files by defining a list of strings stored in a file, along with an index to the position of each string. Note the use of writeInt( ), writeLong( ), and writeUTF( ) to write integers, longs, and strings, and the use of readInt( ), readLong( ), and readUTF( ) to read the corresponding values back. These methods are defined by the DataOutput and DataInput interfaces, which are also implemented by the DataOutputStream and DataInputStream classes. Note also the use of the seek( ) method to set the file position of a RandomAccessFile and the getFilePosition( ) method for querying the current position. Finally, don't forget that, like streams, random-access files must be closed when they are no longer needed.

Example 3-8. WordList.java
package je3.io;
import java.io.*;

/**
 * This class represents a list of strings saved persistently to a file, 
 * along with an index that allows random access to any string in the list.
 * The static method writeWords( ) creates such an indexed list in a file.
 * The class demostrates the use of java.io.RandomAccessFile
 */
public class WordList {
    // This is a simple test method
    public static void main(String args[  ]) throws IOException {
        // Write command-line arguments to a WordList file named "words.data"
        writeWords("words.data", args);
        
        // Now create a WordList based on that file
        WordList list = new WordList("words.data");
        // And iterate through the elements of the list backward
        // This would be very inefficient with sequential-access streams
        for(int i = list.size( )-1; i >= 0; i--) 
            System.out.println(list.get(i));
        // Tell the list we're done with it.
        list.close( );
    }

    // This static method creates a WordList file
    public static void writeWords(String filename, String[  ] words)
        throws IOException
    {
        // Open the file for read/write access ("rw").  We only need to write,
        // but have to request read access as well
        RandomAccessFile f = new RandomAccessFile(filename, "rw");

        // This array will hold the positions of each word in the file
        long wordPositions[  ] = new long[words.length];

        // Reserve space at the start of the file for the wordPositions array
        // and the length of that array. 4 bytes for length plus 8 bytes for
        // each long value in the array.
        f.seek(4L + (8 * words.length));

        // Now, loop through the words and write them out to the file,
        // recording the start position of each word.  Note that the
        // text is written in the UTF-8 encoding, which uses 1, 2, or 3 bytes
        // per character, so we can't assume that the string length equals
        // the string size on the disk.  Also note that the writeUTF( ) method
        // records the length of the string so it can be read by  readUTF( ).
        for(int i = 0; i < words.length; i++) {
            wordPositions[i] = f.getFilePointer( ); // record file position
            f.writeUTF(words[i]);                  // write word
        }

        // Now go back to the beginning of the file and write the positions
        f.seek(0L);                                   // Start at beginning
        f.writeInt(wordPositions.length);             // Write array length
        for(int i = 0; i < wordPositions.length; i++) // Loop through array
            f.writeLong(wordPositions[i]);              // Write array element
        f.close( );   // Close the file when done.
    }

    // These are the instance fields of the WordList class
    RandomAccessFile f;  // the file to read words from
    long[  ] positions;    // the index that gives the position of each word

    // Create a WordList object based on the named file
    public WordList(String filename) throws IOException {
        // Open the random access file for read-only access
        f = new RandomAccessFile(filename, "r");

        // Now read the array of file positions from it
        int numwords = f.readInt( );             // Read array length
        positions = new long[numwords];         // Allocate array
        for(int i = 0; i < numwords; i++)       // Read array contents
            positions[i] = f.readLong( );
    }

    // Call this method when the WordList is no longer needed.
    public void close( ) throws IOException {
        if (f != null) f.close( );  // close file
        f = null;                  // remember that it is closed
        positions = null;
    }

    // Return the number of words in the WordList
    public int size( ) {
        // Make sure we haven't closed the file already
        if (f == null) throw new IllegalStateException("already closed");
        return positions.length;
    }

    // Return the string at the specified position in the WordList
    // Throws IllegalStateException if already closed, and throws
    // ArrayIndexOutOfBounds if i is negative or >= size( )
    public String get(int i) throws IOException {
        // Make sure close( ) hasn't already been called.
        if (f == null) throw new IllegalStateException("already closed");
        f.seek(positions[i]);  // Move to the word position in the file.
        return f.readUTF( );    // Read and return the string at that position.
    }
}
    [ Team LiB ] Previous Section Next Section