|
|
|
Contents: |
|
|
|
Related content: |
|
|
|
Subscriptions: |
|
|
| Learn how to manipulate J2SE 1.4's new I/O
package
John
Zukowski (mailto:jaz@zukowski.net?cc=&subject=The
ins and outs of Merlin's new I/O buffers) President, JZ Ventures,
Inc. 25 March 2003
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.
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
- Participate in the discussion forum on this
article. (You can also click Discuss at the top or bottom of the
article to access the forum.)
- Read the complete Magic
with Merlin column series, which includes details on how to tell
the system to convert
bytes to characters (developerWorks, October 2002).
- Read the java.nio
package's API documentation.
- For more on NIO, read "New I/O
APIs" (Sun Microsystems, 2002).
- Learn more about nonblocking I/O operations in "Merlin
brings nonblocking I/O to the Java platform" (developerWorks,
March 2002).
- For another perspective on NIO, see "Working
XML: Wrestling with Java NIO" from the developerWorks XML
zone (developerWorks, June 2002).
- For a good introduction to Java technology's I/O facilities, take
the "Introduction
to Java I/O" tutorial (developerWorks, April 2000).
- Merlin Hughes shows you how to speed up your applications in his
article "Turning
streams inside out, Part 2: Optimizing internal Java I/O"
(developerWorks, September 2002).
- Find hundreds more Java technology resources on the developerWorks
Java technology zone.
|
|