Skip to main content

Magic with Merlin: The ins and outs of Merlin's new I/O buffers

Learn how to manipulate J2SE 1.4's new I/O package

John Zukowski (jaz@zukowski.net), President, JZ Ventures, Inc.
Author photo
John Zukowski conducts strategic Java consulting with JZ Ventures, Inc. and serves as the resident guru for a number of jGuru's community-driven Java FAQs. His latest books are Learn Java with JBuilder 6 from Apress and Mastering Java 2: J2SE 1.4 from Sybex. Contact John at jaz@zukowski.net.

Summary:  A series of buffer classes underpins the Java 2 Platform Standard Edition's new I/O (NIO) packages. The classes' data containers form the foundation of other NIO operations like nonblocking reads over socket channels. In this month's Magic with Merlin, resident Java programming wizard John Zukowski shows how to manipulate those data buffers for such tasks as reading/writing primitives and working with memory-mapped files. In a later article, he'll expand on the concepts presented here to work with the socket channels.

View more content in this series

Date:  25 Mar 2003
Level:  Introductory
Activity:  1439 views
Comments:  

With Java 2 Platform Standard Edition (J2SE) 1.4 came numerous changes to the Java platform's I/O handling capabilities. Instead of just the previous J2SE releases' continual support for stream-based I/O operations with chaining from stream to stream, Merlin adds new capabilities in what is dubbed the New I/O classes (NIO), which now reside in the java.nio package.

I/O performs input and output operations to transfer data from things like files or the system console into or out of an application. (See Resources for additional information on Java I/O.)

Buffer foundations

The abstract Buffer class forms the foundation of the java.nio package's buffer support. Buffer works like an in-memory RandomAccessFile for reading and writing primitive data types. Like a RandomAccessFile, with Buffer the next operation you perform (read/write) happens at your position. Performing either operation changes that position, so doing a read after a write doesn't read what was just written, but what is after what was just written. Buffer offers four indicators for access to the linear structure (from highest value to lowest):

  • capacity(): Indicates the buffer's size
  • limit(): Tells you how many bytes you've stuffed into the buffer so far, or lets you change that limit with :limit(int newLimit):
  • position(): Tells you the current location to perform the next read/write operation
  • mark(): Lets you remember a position for later resetting with reset()

A buffer's basic operations are get() and put(); those methods, however, are type-specific in subclasses. Having said that, let's examine a quick example to demonstrate writing and reading a char from the same buffer. In Listing 1, the flip() method swaps the limit and position, then sets the position to zero, discarding the mark, letting you read what was just written:


Listing 1. Reading/writing example
import java.nio.*;
...

CharBuffer buff = ...;
buff.put('A');
buff.flip();
char c = buff.get(); 
System.out.println("An A: " + c);

Now let's examine the specific Buffer subclasses.


Buffer types

Merlin features seven specific Buffer types, one for each primitive data type (except boolean):

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

I'll discuss an eighth type, MappedByteBuffer, available for memory-mapped files, later in this article. If you must work with something other than primitives, you can acquire bytes from a ByteBuffer, then convert them into an Object or whatever else.

As previously mentioned, each buffer includes get() and put() methods that offer type-safe versions. Typically, these get() and put() methods are overloaded. For example, with CharBuffer you can get() the next char, get(int index) the char at a specific position, or get(char[] destination) a bunch of chars. Static methods can also create the buffers, because no constructors exist. So, sticking with the CharBuffer example, you can convert a String object to a CharBuffer with CharBuffer.wrap(aString). To demonstrate, Listing 2 takes the first command-line argument, converts it to a CharBuffer, and displays each char in the argument:


Listing 2. CharBuffer demonstration
import java.nio.*;

public class ReadBuff {
  public static void main(String args[]) {
     if (args.length != 0) {
      CharBuffer buff = CharBuffer.wrap(args[0]);
      for (int i=0, n=buff.length(); i<n; i++) {
        System.out.println(i + " : " + buff.get());
      }
    }
  }
}

Notice that I use get(), not get(index). I do so because after each get() operation the position is moved, so you needn't manually state which position to retrieve.


Direct versus nondirect

Now that you've seen the typical buffer, let's examine the difference between direct and nondirect buffers. When you create a buffer, you can request a direct buffer, which costs more to create than nondirect buffers but lets the runtime environment use quicker native I/O operations directly upon the buffer. Because of the added creation cost, use direct buffers only for long-lived buffers, not short, one-time, throwaway buffers. Furthermore, you can create direct buffers only at the ByteBuffer level, where you then must convert the Buffer to the more specific type if you want to treat the contents as a different type. To demonstrate, Listing 3 duplicates the code in Listing 2's behavior, but uses a direct buffer:


Listing 3. List network interfaces
import java.nio.*;

public class ReadDirectBuff {
  public static void main(String args[]) {
     if (args.length != 0) {
      String arg = args[0];
      int size = arg.length();
      ByteBuffer byteBuffer = ByteBuffer.allocateDirect(size*2);
      CharBuffer buff = byteBuffer.asCharBuffer();
      buff.put(arg);
      buff.rewind();
      for (int i=0, n=buff.length(); i<n; i++) {
        System.out.println(i + " : " + buff.get());
      }
    }
  }
}

In the code above, notice that you can't just wrap the String into a direct ByteBuffer. You first must create the buffer, fill it, then rewind the position to the beginning so you can read from the start. Also remember that chars are twice as wide as bytes, hence the size*2 in the example.


Memory-mapped files

The eighth Buffer type, MappedByteBuffer, is simply a special ByteBuffer. MappedByteBuffer maps a region of a file directly in memory. Typically, that region comprises the entire file, although it could map a portion. You must, therefore, specify what part of the file to map. Moreover, as with the other Buffer objects, no constructor exists here; you must ask the java.nio.channels.FileChannel for its map() method to get a MappedByteBuffer. Further, without delving too far into channels, you can just get the FileChannel from a FileInputStream or a FileOutputStream with the getChannel() method. Listing 4 demonstrates MappedByteBuffer by reading the text file's contents in which the filename is passed in from the command line:


Listing 4. Read a memory-mapped text file
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;


public class ReadFileBuff {
  public static void main(String args[]) throws IOException {
     if (args.length != 0) {
      String filename = args[0];
      FileInputStream fis = new FileInputStream(filename);
      FileChannel channel = fis.getChannel();
      int length = (int)channel.size();
      MappedByteBuffer byteBuffer =
        channel.map(FileChannel.MapMode.READ_ONLY, 0, length);
      Charset charset = Charset.forName("ISO-8859-1");
      CharsetDecoder decoder = charset.newDecoder();
      CharBuffer charBuffer = decoder.decode(byteBuffer);
      for (int i=0, n=charBuffer.length(); i<n; i++) {
        System.out.print(charBuffer.get());
      }
    }
  }
}

As I explained in "Character sets" (see Resources), because the file has content, you must tell the system how to convert the bytes to characters. Hence the need to use Charset.


Conclusion

J2SE's new I/O package, underpinned by Buffer, fundamentally changes the way Java technology handles I/O operations. After reading this article, you should understand NIO, from basic get and put operations to reading a memory-mapped file. That, however, should not end your NIO education. Use the resources mentioned here to read up on I/O in the Java platform. In a future article, I'll relate the concepts presented here to working with the socket channels.


Resources

About the author

Author photo

John Zukowski conducts strategic Java consulting with JZ Ventures, Inc. and serves as the resident guru for a number of jGuru's community-driven Java FAQs. His latest books are Learn Java with JBuilder 6 from Apress and Mastering Java 2: J2SE 1.4 from Sybex. Contact John at jaz@zukowski.net.

Comments



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=10788
ArticleTitle=Magic with Merlin: The ins and outs of Merlin's new I/O buffers
publish-date=03252003
author1-email=jaz@zukowski.net
author1-email-cc=jaloi@us.ibm.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers