July 24, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

FileChannel Objects in Java, Using View Objects for Different Types

  • October 29, 2002
  • By Richard G. Baldwin
  • Send Email »
  • More Articles »

Java Programming Notes # 1790


Preface

New features in SDK Version 1.4.0

The recently released JavaTM 2 SDK, Standard Edition Version 1.4 contains a large number of new features, including the concept of IO channels.  The first lesson in this miniseries, entitled FileChannel Objects in Java, Background Information, introduced you to the concept of channels from a read/write IO viewpoint.  The previous lesson, entitled FileChannel Objects in Java, ByteBuffer Type, showed you the basics of read/write programming using channels.

Showing off the advantages of channels

While the sample program in the previous lesson illustrated the basics of using channels, it didn't do a very good job of showing off the advantages of channels.  The sample program in this and subsequent lessons will do a much better job of showing off the advantages.

Different primitive types

In this lesson, I will show you how to use the FileChannel class, the ByteBuffer class, the DoubleBuffer class, and the ShortBuffer class to transfer data of type double and data of type short between the computer's memory and a physical file.  You will learn how to extend the concept to any primitive data type other than boolean. 

Mixed primitive types

In the next lesson, I will show you how to use the FileChannel class along with the ByteBuffer class to create records consisting of sequences of data values of mixed primitive types, and how to transfer those records between the computer's memory and a physical file.

Memory-mapped IO

Future lessons will teach you how to do memory-mapped IO using channels.

Viewing tip

You may find it useful to open another copy of this lesson in a separate browser window.  That will make it easier for you to scroll back and forth among the different listings and figures while you are reading about them.

Supplementary material

I recommend that you also study the other lessons in my extensive collection of online Java tutorials.  You will find those lessons published at Gamelan.com.  However, as of the date of this writing, Gamelan doesn't maintain a consolidated index of my Java tutorial lessons, and sometimes they are difficult to locate there.  You will find a consolidated index at www.DickBaldwin.com.

What is a FileChannel?

Sun describes an object of the FileChannel class simply as "A channel for reading, writing, mapping, and manipulating a file."

Discussion and Sample Code

I will illustrate the FileChannel class using the sample program named Channel02.  You will find a complete listing of the program in Listing 17 near the end of the lesson.  As is my normal approach, I will discuss this program in fragments.

Writing and reading different primitive types

This program, which was tested using Java SDK version 1.4.0 under Win2000, illustrates the use of FileChannel objects to write and read data of different primitive types from a disk file.

Begin with type double

The program begins by first writing and then reading data of type double using a DoubleBuffer view of a ByteBuffer object.

Applicable to primitive types other than boolean

Then the program illustrates that this approach is applicable to any primitive type (other than boolean) by writing and then reading data of type short using a ShortBuffer view of a ByteBuffer object.

The main method

To keep things simple, the program consists of a main method and several static convenience methods.  The beginning of the main method is shown in Listing 1.

  public static void main(
                        String[] args){
    ByteBuffer bBuf = 
        ByteBuffer.wrap(new byte[56]);

Listing 1

Wrap an array object in a ByteBuffer object

The code in Listing 1 uses the wrap method of the ByteBuffer class to create a ByteBuffer object with a capacity of 56 bytes.  Each byte in the buffer is initialized to a value of zero.  (This is not new.  The methodology for creating ByteBuffer objects was discussed in previous lessons.)

Create a DoubleBuffer view of the ByteBuffer object

The code in Listing 2 is new to this series of tutorial lessons, and is very significant.  This code invokes the asDoubleBuffer method on the ByteBuffer object's reference to create a DoubleBuffer view of the ByteBuffer object.

    DoubleBuffer dBuf = 
                 bBuf.asDoubleBuffer();

Listing 2

DoubleBuffer contents and properties

The content of the DoubleBuffer object starts at the ByteBuffer object's current position, (which is zero in this case).

The DoubleBuffer object's position will be zero.  Its capacity and its limit will be the number of bytes remaining in the ByteBuffer object divided by eight.  Its mark will be undefined.  (The concept of the bytes remaining in a ByteBuffer object was discussed in previous lessons.)

Important, changes will be visible ...

It is very important to note that changes to the ByteBuffer object's content will be visible in the DoubleBuffer object and vice versa.

(Although I can't tell you how the virtual machine manages this data from a physical memory viewpoint, the effect is as if each of the two buffers simply provides a different view of the same data.)

However, even though the two buffers appear to provide different views of the same data, the two buffers' position, limit, and mark values will be independent of one another.

Also reflected in the original array object

Although it isn't demonstrated in this program, changes to the original array object's contents will also be visible in the ByteBuffer object, and in the DoubleBuffer object, and vice versa.  Thus, these two objects simply provide different views of the data in the original array object.  Stated differently, the original array object, the ByteBuffer object, and the DoubleBuffer object all provide different views of the same data.

Units of type double

As you will see later, methods invoked on the DoubleBuffer object operate in units of type double, and not in units of type byte.  This makes it easy, for example, to iterate through the DoubleBuffer object, accessing one double value during each iteration.

Populating the DoubleBuffer object

The code in Listing 3 uses the relative put method of the DoubleBuffer class to populate the DoubleBuffer object with six double values.  (Some of the values, such as 0.6666666666666666, were purposely designed to make full use of the precision of the double type.)

    int cnt = 0;
    for(cnt = 0; cnt < 6; cnt++){
      dBuf.put((cnt+1.0)/3);
    }//end for loop

Listing 3

The relative put method

The relative put method writes the specified double value into the buffer at the current position, and then increments the position by one.

(Several other overloaded versions of the put method are available, including an absolute version that allows you to provide an index that specifies the element into which the double value will be written.)

Prepare for writing to a disk file

At this point, I need to prepare the buffer for writing to a disk file.  In particular, I need to set its limit to the number of values that I want to have written to the file. 

The first statement in Listing 4 sets the limit of the DoubleBuffer object to the number of double values that I have written into the buffer.

    dBuf.limit(cnt);
    bBuf.limit(dBuf.limit()*8);

Listing 4

Will actually write the ByteBuffer to the disk file

Actually, I'm not going to write the DoubleBuffer object to the disk file.  Rather, I will use a FileChannel object to write the backing ByteBuffer object to the disk file.

(FileChannel objects don't deal directly with DoubleBuffer objects, FloatBuffer objects, ShortBuffer objects, etc.  Rather, they deal with the backing ByteBuffer object.)

What I really need to do ...

Therefore, what I really need to do is set the limit on the ByteBuffer object to correspond to the number of bytes consumed by the double values written into the DoubleBuffer object.

After setting the limit on the DoubleBuffer object, the second statement in Listing 4 sets the limit on the backing ByteBuffer object to eight times the limit of the DoubleBuffer object (remember, each double value consists of eight bytes).

Display the DoubleBuffer data

The code in Listing 5 invokes the showDoubleBufferData method to display the contents of the DoubleBuffer object.

    showDoubleBufferData(
                 dBuf,"dBuf-raw data");
                 
Listing 5

The showDoubleBufferData method

The entire showDoubleBufferData method is shown in Listing 6.

  static void showDoubleBufferData(
        DoubleBuffer buf, String name){
    //Displays buffer contents
    
    //Save position
    int pos = buf.position();
    //Set position to zero
    buf.position(0);
    System.out.println(
                   "Data for " + name);
    while(buf.hasRemaining()){
      System.out.println(buf.get());
    }//end while loop
    System.out.println();//new line
    //Restore position and return
    buf.position(pos);
  }//end showDoubleBufferData

Listing 6

Similar to a previous lesson

This method is essentially the same as a similar method that I explained in the previous lesson.  The most significant difference is the fact that the iterative loop (shown in boldface) iterates through the object in eight-byte groups instead of iterating on individual bytes (as was the case in the previous lesson). 

Thus, each invocation of the relative get method retrieves an entire double value (eight bytes) from the DoubleBuffer object, and then increments the value of the position property of the DoubleBuffer object.

The output

The output produced by the code in Listing 6 is shown in Figure 1.

Data for dBuf-raw data
0.3333333333333333
0.6666666666666666
1.0
1.3333333333333333
1.6666666666666667
2.0

Figure 1

Hopefully this is what you expected to see for double data based on division by three.

Return to the main method

Now, I'll return the discussion to the flow of control in the main method.

Getting the size of a disk file

Later on, I'm going to write code to display the number of bytes in a disk file.  I can do that by invoking the size method on a FileChannel object.  I can also do that, (independently of the FileChannel object) by using a File object that represents the file under investigation.

(In this lesson, I chose to take the independent route and use a File object instead of invoking the size method on the FileChannel object.)

Get and save an object of type File

The code in Listing 7 gets a File object that will represent a physical file named junk.txt (when such a file comes into existence).

    String fileName = "junk.txt"; 
    File file = new File(fileName);

Listing 7

(As you may recall, a File object that represents a physical disk file can provide information about the file that it represents, such as its size, its location in the disk file structure, etc.  Note that the use of the File object is not required for the proper operation of this program, other than to make it easy to determine the size of the file.)

Local variables

The code in Listing 8 declares three local reference variables, which will be used later.

    FileOutputStream oStr;
    FileInputStream iStr;
    FileChannel fileChan;

Listing 8

The variables declared in Listing 8 will be used later to hold references to input and output file stream objects, as well as holding a reference to a FileChannel object.

Get FileChannel object for output

In the previous lesson, I showed you that the methodology for getting a FileChannel object (to be used for output only) is:

  • Get a FileOutputStream object.
  • Invoke the getChannel method on the FileOutputStream object.

The code in Listing 9 uses this methodology to get a FileChannel object that can be used to transfer data from a ByteBuffer object to the file named junk.txt.

    //Get FileChannel for output
    try{
      oStr = new FileOutputStream(
                             fileName);
      fileChan = oStr.getChannel();

Listing 9

Write the data to the disk file

The code in Listing 10 invokes the write method of the FileChannel class to write the output data from the ByteBuffer object to the disk file.

      System.out.println(
               "Bytes written = " 
               + fileChan.write(bBuf));

      //Close stream and channel
      oStr.close();
      fileChan.close();

Listing 10

Important, also writes DoubleBuffer data to file

In the process of writing the data from the ByteBuffer object to the disk file, the code in Listing 10 also writes the data from the DoubleBuffer object to the disk file (remember, these are simply different views of the same data).





Page 1 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel