Main Page

Previous Next

Reading Binary Data

When you read binary data you still read bytes from the file, so the process is essentially the same as we used in the previous example. To read a binary file, we create a FileInputStream object and get the FileChannel object from it, then read the data into a byte buffer. We could set up a file channel to read our primes.bin file like this:

File aFile = new File("C:/Beg Java Stuff/primes.bin");
FileInputStream inFile = null;
   
try {
  inFile = new FileInputStream(aFile); 

} catch(FileNotFoundException e) {
  e.printStackTrace(System.err);
  System.exit(1);
}
FileChannel inChannel = inFile.getChannel();    

We have some options on the size of the byte buffer. The number of bytes in the buffer should be a multiple of eigth since a prime value is of type long but other than that we can make it whatever size we like. We could allocate a buffer to accommodate the number of primes that we want to output to the command line, six values say. This would make accessing the data very easy since we only need to set up a view buffer of type LongBuffer each time we read from the file. One thing against this is that reading such a small amount of data from the file in each read operation would not be a very efficient way to read the file. Before data transfer can start for a read operation there is a significant delay, usually of the order of several milliseconds, waiting for the disk to rotate until the data that we want to read is under the read heads. Therefore the more read operations you use to retrieve a given amount of data from the file the longer it takes. However, in the interests of understanding the mechanics of this let's see how it would work anyway. The buffer would be created like this:

final int PRIMECOUNT = 6;                  // Number of primes to read at a time    
ByteBuffer buf = ByteBuffer.allocate(8*PRIMECOUNT);    

We can then read the primes in a while loop inside a try block, like this:

long[] primes = new long[PRIMECOUNT];    
try {
  primes = new long[(int)inChannel.size()/8];    // Array to hold 5 primes
  while(inChannel.read(buf) != -1) {
    // Access the primes via a view buffer of type LongBuffer...
    // Output the primes read...
    buf.clear();                    // Clear the buffer for the next read
  }
  System.out.println("EOF reached.");
  inFile.close();                   // Close the file and the channel
 
} catch(IOException e) {
  e.printStackTrace(System.err);
  System.exit(1);
}

We can create a view buffer of type LongBuffer that will help us get at the primes. We can obtain the view buffer by calling the asLongBuffer() method for the byte buffer, buf. The LongBuffer class offers you a choice of four get() methods for accessing long values in the buffer:

get()

Extracts a single value of type long from the buffer at the current position and returns it. The buffer position is then incremented by 1.

get(int index)

Extracts a single value of type long from the buffer position specified by the argument and returns it. The current buffer position is not altered.

get(long[] values)

Extracts values.length values of type long from the buffer starting at the current position and stores them in the array values. The current position is incremented by the number of values retrieved from the buffer. The method returns a reference to the buffer as type LongBuffer. If there are insufficient values available from the buffer to fill the array that you pass as the argument – in other words, limit-position is less than values.length – the method will throw an exception of type BufferUnderflowException and no values will be transferred to the array and the buffer's position will be unchanged.

get(long[] values,

int offset,

int length)

Extracts length values of type long from the buffer starting at the current position and stores them in the array values, starting at values[offset]. The current position is incremented by the number of values retrieved from the buffer. The method returns a reference to the buffer as type LongBuffer. If there are insufficient values available from the buffer – in other words, limit-position is less than length the method will throw an exception of type BufferUnderflowException no values will be transferred to the array, and the buffer's position will be unchanged.

The BufferUnderflowException class is a subclass of RuntimeException so you are not obliged to catch this exception, although it may be useful to do so if you want to avoid references to array elements that have not been loaded with data from the buffer.

With the buffer size we have chosen, perhaps the simplest way to access the primes in the buffer is like this:

LongBuffer longBuf = ((ByteBuffer)(buf.flip())).asLongBuffer();
System.out.println();                        // Newline for the buffer contents
while(longBuf.hasRemaining())                // While there are values
  System.out.print("  " + longBuf.get());    // output them on the same line

If we wanted to collect the primes into an array, the form of get() method that transfers values to an array is more efficient than writing a loop to transfer them one at a time, but we have to be careful. Let's try it out in an example to see why.

Try It Out – Reading a Binary File

We will choose to read the primes six at a time into an array. Here's the program:

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

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

    } catch(FileNotFoundException e) {
      e.printStackTrace(System.err);
      System.exit(1);
    }
  
    FileChannel inChannel = inFile.getChannel();    
    final int PRIMECOUNT = 6;
    ByteBuffer buf = ByteBuffer.allocate(8*PRIMECOUNT);  
    long[] primes = new long[PRIMECOUNT];
    try {
      while(inChannel.read(buf) != -1) {
        ((ByteBuffer)(buf.flip())).asLongBuffer().get(primes);

        System.out.println();
        for(int i = 0 ; i<primes.length ; i++)
           System.out.print("  " + primes[i]);
         
        buf.clear();                    // Clear the buffer for the next read
      }
      System.out.println("\nEOF reached.");
      inFile.close();                   // Close the file and the channel
 
    } catch(IOException e) {
      e.printStackTrace(System.err);
      System.exit(1);
    }
      System.exit(0);
  }
}

We get a whole lot of prime values, six to a line, then, when we almost have them all displayed, we suddenly get the output:

... 
467 479 487 491 499 503Exception in thread "main" java.nio.BufferUnderflo wException at java.nio.LongBuffer.get(LongBuffer.java:609) at java.nio.LongBuffer.get(LongBuffer.java:633) at ReadPrimes.main(ReadPrimes.java:28)

How It Works

The reason is doesn't work very well is that the number of primes in the file is not divisible by the number of primes that we read into the view buffer. This is determined by the number of elements in the array primes. On the last iteration of the while loop that reads the file, there are insufficient values to fill the array so the get() method throws an exception of type BufferUnderflowException.

One way to deal with this is to catch the exception that is thrown. It's not a particularly good way because of the overhead in throwing and catching exceptions, but let's see how we could do it anyway. We could rewrite the while loop like this:

int primesRead = 0;
while(inChannel.read(buf) != -1) {
try {
  ((ByteBuffer)(buf.flip())).asLongBuffer().get(primes);
  primesRead = primes.length;

} catch(BufferUnderflowException e) {
  LongBuffer longBuf = buf.asLongBuffer();
  primesRead = longBuf.remaining();
  longBuf.get(primes,0, primesRead);
}

System.out.println();
for(int i = 0 ; i< primesRead ; i++)
  System.out.print("  "+primes[i]);
         
  buf.clear();                    // Clear the buffer for the next read
}

When the exception is thrown on the last iteration, we catch it and read the remaining values in the view buffer using the alternate form of the get() method, where the second argument specifies the first array element to store a value in and the third argument specifies the number to be stored. To take account of the possibility that less than the whole array will contain primes when we output it, we set the number of primes that are read in the loop. Note that we must set the value of primesRead inside the catch block before we execute the get() method. Afterwards the number remaining will be zero.

Of course, although this works, it is a very poor way to deal with the problem. A better way is to avoid it altogether, like this:

int primesRead = 0;
while(inChannel.read(buf) != -1) {
  LongBuffer longBuf = ((ByteBuffer)(buf.flip())).asLongBuffer();
  primesRead = longBuf.remaining();
  longBuf.get(primes,0, longBuf.remaining());
  System.out.println();
  for(int i = 0 ; i< primesRead ; i++)
    System.out.print("  "+primes[i]);
   
  buf.clear();                    // Clear the buffer for the next read
}

The shaded lines reflect changes to the code in the original example. Now we always read the number of values available in longBuf so we can't cause the BufferUnderflowException to be thrown.

A further possibility is to use a buffer large enough to hold all the primes in the file. We can work this out from the value returned by the size() method for the channel – which is the length of the file in bytes. We could do that like this:

    final int PRIMECOUNT = (int)inChannel.size()/8; 

Of course, you also must alter the for loop that outputs the primes so it doesn't attempt to put them all on the same line. There is a hazard with this though if you don't know how large the file is. Unless your PC is unusually replete with RAM, it could be inconvenient if the file contains the first billion primes. It might be as well to put an assertion to protect against an excess of primes:

assert inChannel.size()<=100000;
final int PRIMECOUNT = (int)inChannel.size()/8;

Now the program will not proceed if there are more than 100,000 primes in the file. Don't forget, to compile a program with assertions you must specify the -source 1.4 options, and when you execute the program you need to specify the -enableassertions option.

Making the Output Pretty

One final point before we leave this example – the output is irritating. Why don't the columns line up? Well they should and could, but it's a bit more code that would clutter up the example. However, suppose we want to output the primes six to a line, left justified in a field width of 12. Here's one way we could do that:

StringBuffer str = null;        
for(int i = 0 ; i< primesRead ; i++) {
  str = new StringBuffer("           ").append(primes[i]);          
  System.out.print((i%6 == 0 ? "\n" : "") + str.substring(str.length()-12, 
                    str.length()));
}

This replaces the loop in the original code. On the first and every sixth prime output we start a new line by outputting "\n" as the first character in the argument to the print() method. We create a StringBuffer object, which contains 11 spaces, and append the String representation of the prime value to it. We then just output the string consisting of the last 12 characters in the StringBuffer object.

Previous Next
JavaScript Editor Java Tutorials Free JavaScript Editor