Main Page

Previous Next

Random Access to a File

We can already read or write a file at random. The FileChannel class defines both a read()and a write() method that operate at a specified position in the file:

read(ByteBuffer buf, long position)

Reads bytes from the file into buf in the same way as we have seen previously except that bytes are read starting at the file position specified by the second argument. The channel's position is not altered by this operation. If position is greater than the number of bytes in the file then no bytes are read.

write(ByteBuffer buf, long position)

Writes bytes from buf to the file in the same way as we have seen previously except that bytes are written starting at the file position specified by the second argument. The channel's position is not altered by this operation. If position is less than the number of bytes in the file then bytes from that point will be overwritten. If position is greater than the number of bytes in the file then the file size will be increased to this point before bytes are written. In this case the bytes between the original end-of-file and where the new bytes are written will contain junk values.

These methods can throw the same exceptions as the corresponding method accepting a single argument, plus they may throw an exception of type IllegalArgumentException if a negative file position is specified.

Let's see how we can access a file randomly using the read() method above.

Try It Out – Reading a File Randomly

To show how easy it is to read from random positions in a file, we will write an example to extract a random selection of values from our primes.bin file. Here's the code:

import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;

public class RandomFileRead {
  public static void main(String[] args) {
    File aFile = new File("C:/Beg Java Stuff/primes.bin");
    FileInputStream inFile = null;
    FileOutputStream outFile = null;
   
    try {
      inFile = new FileInputStream(aFile); 

    } catch(FileNotFoundException e) {
      e.printStackTrace(System.err);
      System.exit(1);
    }
    FileChannel inChannel = inFile.getChannel();
    
    final int PRIMESREQUIRED = 10;
    ByteBuffer buf = ByteBuffer.allocate(8*PRIMESREQUIRED);  

    long[] primes = new long[PRIMESREQUIRED];
    int index = 0;                           // Position for a prime in the file

    try {
      // Count of primes in the file
      final int PRIMECOUNT = (int)inChannel.size()/8;  

      // Read the number of random primes required
      for(int i = 0 ; i<PRIMESREQUIRED ; i++) {
        index = 8*(int)(PRIMECOUNT*Math.random());  
        inChannel.read(buf, index);                   // Read the value 
        buf.flip();
        primes[i] = buf.getLong();                    // Save it in the array
        buf.clear();
      }

      // Output the selection of random primes 5 to a line in field width of 12
      StringBuffer str = null;        
      for(int i = 0 ; i<PRIMESREQUIRED ; i++) {
        str = new StringBuffer("           ").append(primes[i]);          
          System.out.print((i%5 == 0 ? "\n" : "") 
                         + str.substring(str.length()-12, str.length()));
      }
      inFile.close();                   // Close the file and the channel
 
    } catch(IOException e) {
      e.printStackTrace(System.err);
      System.exit(1);
    }
      System.exit(0);
  }
}

When I ran this, I got the output:

         359         107         383         109             7
         173         443         337          17           113

You should get something similar, but not the same since the random number generator is seeded using the current clock time.

How It Works

We access a random prime in the file by generating a random position in the file with the expression 8*(int)(PRIMECOUNT*Math.random()). The value of index is a pseudo-random integer that can be from 0 to the number of primes in the file minus one, multiplied by 8 since each prime occupies eight bytes. Since buf has a capacity of 8 bytes, only one prime will be read each time. We store a randomly selected prime in each element of the primes array. Finally we output the primes five to a line in a field width of 12 characters.

The need to be able to access and update a file randomly arises quite often. Even with a simple personnel file for example you are likely to need the capability to update the address or the phone number for an individual. Assuming you have arranged for the address and phone number entries to be of a fixed length, you could update the data for any entry simply by overwriting it. If we want to read and write the same file we can just create two file streams and get two file channels for the file, one for input and one for output. Let's try that too.

Try It Out – Reading and Writing a File Randomly

We can modify the previous example so we overwrite each random prime that we retrieve with the value 99999L to make it stand out from the rest. This will mess up the primes.bin file but you can always run the program that created it again if you want to restore it. Here's the code:

import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;

public class RandomReadWrite {
  public static void main(String[] args)
  {
    File aFile = new File("C:/Beg Java Stuff/primes.bin");
    FileInputStream inFile = null;
    FileOutputStream outFile = null;
   
    try {
      inFile = new FileInputStream(aFile); 
      outFile = new FileOutputStream(aFile, true); 

    } catch(FileNotFoundException e) {
      e.printStackTrace(System.err);
      System.exit(1);
    }
    FileChannel inChannel = inFile.getChannel();
    FileChannel outChannel = outFile.getChannel();
    
    final int PRIMESREQUIRED = 10;
    ByteBuffer buf = ByteBuffer.allocate(8*PRIMESREQUIRED);  

    long[] primes = new long[PRIMESREQUIRED];
    int index = 0;                            // Position for a prime in the file
    final long REPLACEMENT = 99999L;          // Replacement for a selected prime   

    try {
      final int PRIMECOUNT = (int)inChannel.size()/8;
      System.out.println("Prime count = "+PRIMECOUNT);
      for(int i = 0 ; i<PRIMESREQUIRED ; i++) {
        index = 8*(int)(PRIMECOUNT*Math.random());  
        inChannel.read(buf, index);  
        buf.flip();
        primes[i] = buf.getLong();
        buf.flip();
        buf.putLong(REPLACEMENT);
        buf.flip();
        outChannel.write(buf, index);
        buf.clear();
      }
 
        StringBuffer str = null;        
      for(int i = 0 ; i<PRIMESREQUIRED ; i++) {
        str = new StringBuffer("           ").append(primes[i]);          
          System.out.print((i%5 == 0 ? "\n" : "")  
                         + str.substring(str.length()-12, str.length()));
      }
      inFile.close();                   // Close the file and the channel
      outFile.close();

    } catch(IOException e) {
      e.printStackTrace(System.err);
      System.exit(1);
    }
      System.exit(0);
  }
}

This will produce a set of ten random prime selections from the file. If you want to verify that we have indeed overwritten these values in the file you can run the ReadPrimes example that we wrote earlier in this chapter.

How It Works

All we had to do to write the file as well as read it was to create a FileOutputStream object for the file in addition to the FileInputStream object and access its file channel. We are then able to use one channel for writing to the file and the other for reading it. We can read and write sequentially or at random. The channel read() and write() methods we are using here explicitly specify the position where the data is to be read or written as an argument. In this case the file position recorded by the channel does not change. We could equally well change the file position for the channel before performing the read or write, like this:

for(int i = 0 ; i<PRIMESREQUIRED ; i++) {
  index = 8*(int)(PRIMECOUNT*Math.random());  
  inChannel.position(index);                    // Set the file position
  inChannel.read(buf);                          // and read from the file  
  buf.flip();
  primes[i] = buf.getLong();
  buf.flip();
  buf.putLong(REPLACEMENT);
  buf.flip();
  outChannel.position(index);                   // Set the file position
  outChannel.write(buf);                        // and write to the file  
  buf.clear();
}

Now the file positions recorded by the channels are set explicitly and each channel's position is updated when it executes an I/O operation. Note that the position() method for a channel does not return a reference to the channel object so we cannot chain the position() and read() method calls together. You can only do this with buffer objects.

One problem with the example as it stands is that some of the selections could be 99999L, which is patently not prime. We could fix this by checking each value we store in the primes array:

for(int i = 0 ; i<PRIMESREQUIRED ; i++)
{
  while(true)
  {
    index = 8*(int)(PRIMECOUNT*Math.random());    
    inChannel.position(index);                    // Set the file position
    inChannel.read(buf);                          // and read from the file  
    buf.flip();
    primes[i] = buf.getLong();
    if(primes[i] != REPLACEMENT)
      break;
    else
      buf.clear();
  }
  buf.flip();
  buf.putLong(REPLACEMENT);
  buf.flip();
  outChannel.position(index);                   // Set the file position
  outChannel.write(buf);                        // and write to the file  
  buf.clear();
}

The while loop now continues if the value read from the file is the same as REPLACEMENT, so another random file position will be selected. This continues until something other than the value of REPLACEMENT is found. Of course, if you run the example often enough, there won't be enough primes in the file to fill the array so the program will loop indefinitely looking for something other than REPLACEMENT. There are several ways you could deal with this. For instance, you could count how many iterations have occurred in the while loop and bail out if it reaches the number of primes in the file. You could also inspect the file first to see whether there are sufficient primes in the file to fill the array. If there are exactly 10 you can fill the array immediately. I'll leave it to you to fill in these details.

Previous Next
JavaScript Editor Java Tutorials Free JavaScript Editor