Return-Path: Received: from pacific-carrier-annex.mit.edu by po10.mit.edu (8.9.2/4.7) id OAA02516; Tue, 7 May 2002 14:32:55 -0400 (EDT) Received: from hermes.sun.com (hermes.sun.com [64.124.140.169]) by pacific-carrier-annex.mit.edu (8.9.2/8.9.2) with SMTP id OAA03994 for ; Tue, 7 May 2002 14:29:47 -0400 (EDT) Date: Tue, 7 May 2002 10:29:47 GMT-08:00 From: "JDC Tech Tips" To: alexp@mit.edu Message-Id: <15019588-1203339468@hermes.sun.com> Subject: JDC Tech Tips, May 7, 2002 (File Channels, Stack Trace Elements) Precedence: junk Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit X-Mailer: Beyond Email J D C T E C H T I P S TIPS, TECHNIQUES, AND SAMPLE CODE WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips, May 7, 2002. This issue covers: * File Channels * Stack Trace Elements These tips were developed using Java(tm) 2 SDK, Standard Edition, v 1.4. You can view this issue of the Tech Tips on the Web at http://java.sun.com/jdc/JDCTechTips/2002/tt0507.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - FILE CHANNELS Channels are a new Java library feature, part of the new I/O package (java.nio). The documentation for channels (http://java.sun.com/j2se/1.4/docs/api/java/nio/channels/Channel.html) defines a channel like this: A channel represents an open connection to an entity such as a hardware device, a file, a network socket, or a program component that is capable of performing one or more distinct I/O operations, for example reading or writing. The java.nio package has many important features in it, including buffers, file channels, other kinds of channels such as socket channels, and extensible interfaces. In this tip, the focus is on file channels only. An important benefit of using file channels is additional I/O functionality in your applications. This tip presents several examples of what this benefit means in practice. Suppose that you have some legacy data in binary files, and this data includes some 16-bit numbers written by the following C program: /* cd1.c */ #include #include short data[] = {1234, 2345, 3456, 4567, 5678}; #define SIZESHORT 2 int main() { int i, j; /* open data file for writing */ FILE* fp = fopen("data", "wb"); assert(fp); /* write out each short value, low byte first */ for (i = 0; i < sizeof(data) / SIZESHORT; i++) { short item = data[i]; for (j = 0; j < SIZESHORT; j++) { char c = (char)(item & 0xff); fputc(c, fp); item >>= 8; } } fclose(fp); return 0; } This program writes five 16-bit values to a file. Each value is written as two bytes, with the low byte written first (this ordering of low byte first is referred to as "little-endian"). Compile, link, and run the C program to produce the data file. How do you read this data using a Java program? Here's one way: import java.io.*; public class ChannelDemo0 { public static void main(String args[]) throws IOException { FileInputStream fis = new FileInputStream("data"); DataInputStream dis = new DataInputStream(fis); short s = dis.readShort(); System.out.println(s); dis.close(); } } This program uses the DataInputStream class and the readShort method of that class. Unfortunately, however, if you run the ChannelDemo0 program, the result is: -11772 This does not correspond to the first value (1234) written into the data file. The problem is that the C program writes the short values as low byte / high byte, and the readShort method expects high byte / low byte. How do you solve this problem? Here's another approach, one that reads the values from the data file and computes their sum: import java.nio.*; import java.nio.channels.*; import java.io.*; public class ChannelDemo1 { // sum the values of short data items in a file static short sumFileContents(String fn) throws IOException { // open input stream and get channel FileInputStream fis = new FileInputStream(fn); FileChannel fc = fis.getChannel(); // map the file into a byte buffer MappedByteBuffer mbb = fc.map( FileChannel.MapMode.READ_ONLY, 0, fc.size()); // set byte order to be little-endian mbb.order(ByteOrder.LITTLE_ENDIAN); // get short view buffer of byte buffer ShortBuffer sb = mbb.asShortBuffer(); // sum up the values short sum = 0; while (sb.hasRemaining()) { sum += sb.get(); } // finish up fc.close(); fis.close(); return sum; } public static void main(String args[]) throws IOException { short sum = sumFileContents("data"); System.out.println(sum); } } The sumFileContents method first creates a FileInputStream, and then gets a file channel based on this stream. A similar approach is used for output streams (FileOutputStream) or for files that are open for both reading and writing (RandomAccessFile). After the method gets the channel, it maps the data file into a MappedByteBuffer. This means that the buffer's content is exactly the file's content, so reading the buffer fetches bytes from the file and writing the buffer stores bytes into the file. The byte buffer is then specified to be little-endian, changing the default order from big-endian. The method then creates a short "view buffer" on the byte buffer. The view buffer presents a view of the mapped byte buffer as a sequence of short (16-bit) values. The short values are each composed of two bytes, and the low-order byte is assumed to come first, so as to match what the C program writes. The view buffer is backed by the byte buffer. So what the method is doing is creating a view of the data file as a sequence of little-endian short values. The final step in the method is to sum up the values. Run the ChannelDemo1 program. It should produce the result: 17280 Let's go on and look at another example of mapping files. Suppose you want to reverse the bytes in a file. How can you do this? One simple way uses the following approach: import java.nio.*; import java.nio.channels.*; import java.io.*; public class ChannelDemo2 { public static void main(String args[]) throws IOException { // check command-line argument if (args.length != 1) { System.err.println( "missing file argument"); System.exit(1); } // get channel RandomAccessFile raf = new RandomAccessFile(args[0], "rw"); FileChannel fc = raf.getChannel(); // map file to buffer MappedByteBuffer mbb = fc.map( FileChannel.MapMode.READ_WRITE, 0, fc.size()); // reverse bytes of file int len = (int)fc.size(); for ( int i = 0, j = len - 1; i < j; i++, j--) { byte b = mbb.get(i); mbb.put(i, mbb.get(j)); mbb.put(j, b); } // finish up fc.close(); raf.close(); } } This program opens a channel based on a RandomAccessFile object, and maps the file for reading and writing. It then sets up a loop that exchanges bytes starting at the two ends of the buffer, and works toward the middle. Because the MappedByteBuffer is mapped to the file on disk, changes to the buffer are reflected in the file. If you enter the commands: javac ChannelDemo2.java java ChannelDemo2 ChannelDemo2.java and then look at the text of ChannelDemo2.java, you will find that all of the bytes in ChannelDemo2.java are reversed. In other words, the first line of the file will be: } and the last line of the file will be: ;*.oin.avaj tropmi To restore the ChannelDemo2.java file to its original byte order, you need to enter the command: java ChannelDemo2 ChannelDemo2.java Mapping a file can be very useful, but it doesn't necessarily free you from doing additional work. For example, suppose that you map a file to get convenient access to the last few bytes of the file. There is a certain amount of intrinsic work that you need to do in this case, such as doing a disk seek to the end of the file, and reading a file block into memory. There's no getting around this work. But mapping is certainly convenient, and sometimes it can be faster than the alternatives. For example, it can help you avoid system call overhead or buffer copies. Let's look at several ways of copying one file to another using file channel features. The first approach is another example of mapped files: import java.nio.*; import java.nio.channels.*; import java.io.*; public class ChannelDemo3 { public static void main(String args[]) throws IOException { // check command-line arguments if (args.length != 2) { System.err.println("missing filenames"); System.exit(1); } // get channels FileInputStream fis = new FileInputStream(args[0]); FileOutputStream fos = new FileOutputStream(args[1]); FileChannel fcin = fis.getChannel(); FileChannel fcout = fos.getChannel(); // map input file MappedByteBuffer mbb = fcin.map( FileChannel.MapMode.READ_ONLY, 0, fcin.size()); // do the file copy fcout.write(mbb); // finish up fcin.close(); fcout.close(); fis.close(); fos.close(); } } In this example, the input file channel is mapped to a buffer, and then that buffer is written to the output channel. Because the buffer represents the whole file, writing the buffer is equivalent to copying the file. So if you enter the commands: javac ChannelDemo3.java java ChannelDemo3 ChannelDemo3.java ChannelCopy3.java It copies the ChannelDemo3 file to ChannelCopy3.java. Another way of copying a file looks like this: import java.nio.*; import java.nio.channels.*; import java.io.*; public class ChannelDemo4 { public static void main(String args[]) throws IOException { // check command-line arguments if (args.length != 2) { System.err.println("missing filenames"); System.exit(1); } // get channels FileInputStream fis = new FileInputStream(args[0]); FileOutputStream fos = new FileOutputStream(args[1]); FileChannel fcin = fis.getChannel(); FileChannel fcout = fos.getChannel(); // do the file copy fcin.transferTo(0, fcin.size(), fcout); // finish up fcin.close(); fcout.close(); fis.close(); fos.close(); } } The transferTo method transfers bytes from the source channel (fcin) to the specified target channel (fcout). The transfer is typically done without explicit user-level reads and writes of the channel. The documentation for the transferTo method (http://java.sun.com/j2se/1.4/docs/api/java/nio/channels/ FileChannel.html#transferTo(long, long, java.nio.channels.WritableByteChannel)) says: This method is potentially much more efficient than a simple loop that reads from this channel and writes to the target channel. Many operating systems can transfer bytes directly from the filesystem cache to the target channel without actually copying them. In other words, transferTo may rely on special operating system features that support very fast file transfers. What does a "regular" file copy look like using file channels? Here's an example: import java.io.*; import java.nio.*; import java.nio.channels.*; public class ChannelDemo5 { public static void main(String args[]) throws IOException { // check command-line arguments if (args.length != 2) { System.err.println("missing filenames"); System.exit(1); } // get channels FileInputStream fis = new FileInputStream(args[0]); FileOutputStream fos = new FileOutputStream(args[1]); FileChannel fcin = fis.getChannel(); FileChannel fcout = fos.getChannel(); // allocate buffer ByteBuffer buf = ByteBuffer.allocateDirect(8192); // do copy long size = fcin.size(); long n = 0; while (n < size) { buf.clear(); if (fcin.read(buf) < 0) { break; } buf.flip(); n += fcout.write(buf); } // finish up fcin.close(); fcout.close(); fis.close(); fos.close(); } } This program copies one file to another, one buffer at a time. Before it reads each chunk of the input file into the buffer, the program "clears" the buffer. This makes the buffer ready for reading by setting the position to 0 and the limit to the capacity. Then, after each read, the program "flips" the buffer. This makes the buffer ready for writing by setting the limit to the current position and the current position to 0. At this point, you've seen some of the new features offered by file channels. There are other features, such as locking, that are also important to learn about. Beyond the new features, file channels offer significant I/O performance gains. For more information about file channels, see New I/O APIs (http://java.sun.com/j2se/1.4/docs/guide/nio/index.html). - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - STACK TRACE ELEMENTS The standard Java library has long had a mechanism for displaying stack tracebacks using the Throwable.printStackTrace method. This method is used to dump the program context for uncaught exceptions. The trace is printed on the System.err stream or on a specified PrintStream or PrintWriter. A new library feature gives you programmatic access to stack tracebacks. You can retrieve an array of StackTraceElement objects, with each object representing a single stack frame in a trace. Let's look at an example to see how this works: class A { B bref; void f() { bref.g(); } } class B { void g() {} } class C { String str; int len = str.length(); } public class TraceDemo1 { // dump a single stack trace element static void dumpTraceElement( StackTraceElement ste) { System.err.println("filename = " + ste.getFileName()); System.err.println("line number = " + ste.getLineNumber()); System.err.println("class name = " + ste.getClassName()); System.err.println("method name = " + ste.getMethodName()); System.err.println("is native method = " + ste.isNativeMethod()); } // dump an array of stack trace elements, // most recent first static void dumpTrace(Throwable e) { // display exception System.err.println("Exception: " + e); System.err.println(); // display traceback StackTraceElement ste[] = e.getStackTrace(); for (int i = 0; i < ste.length; i++) { dumpTraceElement(ste[i]); System.err.println(); } } public static void main(String args[]) { // call A.f() and trigger an exception try { A aref = new A(); aref.f(); } catch (Throwable e) { // display regular stack trace e.printStackTrace(); System.err.println(); // dump stack trace in custom format dumpTrace(e); } System.err.println(); System.err.println( "=============================="); System.err.println(); // trigger an exception in initialization try { new C(); } catch (Throwable e) { dumpTrace(e); } } } In this example, the TraceDemo1 program first calls the A.f method. That method, in turn, calls B.g. Unfortunately, when B.g is called, the B object reference is null. This triggers an exception. The first thing the program prints is the regular stack trace, which looks like this: java.lang.NullPointerException at A.f(TraceDemo1.java:5) at TraceDemo1.main(TraceDemo1.java:61) Then it displays a customized stack traceback, with the exception name and a sequence of StackTraceElements: Exception: java.lang.NullPointerException filename = TraceDemo1.java line number = 5 class name = A method name = f is native method = false filename = TraceDemo1.java line number = 61 class name = TraceDemo1 method name = main is native method = false Notice that the filename, class name, and method name of the first StackTraceElement refer to the point of the exception. The exception occurred at line 5 of TraceDemo1.java, within method A.f. The second part of the example shows what happens when an exception is thrown during instance initialization. In this example, the TraceDemo1 program creates a C object. When the C object is instantiated, it attempts to take the length of the str string. However, because str is never explicitely initialized, the reference to str is a null reference. Here's the output for that part of the example: Exception: java.lang.NullPointerException filename = TraceDemo1.java line number = 15 class name = C method name = is native method = false filename = TraceDemo1.java line number = 83 class name = TraceDemo1 method name = main is native method = false The point of the exception is line 15 of TraceDemo1.java, in the C class, in a method called , which is a special name for instance initialization methods within the Java virtual machine*. You can use the StackTraceElement feature to implement customized exception reporting and logging formats. An example of a customized logging format is one that limits the stack trace to a manageable depth. For example, if there are 500 stack frames, you might want to preserve the first ten and the last ten. The StackTraceElement information is part of the serialized representation of Throwable class instances, so it's available for deserialized objects. For more information about stack trace elements, see the description of the StackTraceElement class at http://java.sun.com/j2se/1.4/docs/api/java/lang/StackTraceElement.html. Also, see section 10.12, Threads and Exceptions, in "The Java(tm) Programming Language Third Edition" by Arnold, Gosling, and Holmes http://java.sun.com/docs/books/javaprog/thirdedition/. . . . . . . . . . . . . . . . . . . . . . . . IMPORTANT: Please read our Terms of Use, Privacy, and Licensing policies: http://www.sun.com/share/text/termsofuse.html http://www.sun.com/privacy/ http://developer.java.sun.com/berkeley_license.html * FEEDBACK Comments? Send your feedback on the JDC Tech Tips to: jdc-webmaster@sun.com * SUBSCRIBE/UNSUBSCRIBE - To subscribe, go to the subscriptions page, (http://developer.java.sun.com/subscription/), choose the newsletters you want to subscribe to and click "Update". - To unsubscribe, go to the subscriptions page, (http://developer.java.sun.com/subscription/), uncheck the appropriate checkbox, and click "Update". - To use our one-click unsubscribe facility, see the link at the end of this email: - ARCHIVES You'll find the JDC Tech Tips archives at: http://java.sun.com/jdc/TechTips/index.html - COPYRIGHT Copyright 2002 Sun Microsystems, Inc. All rights reserved. 901 San Antonio Road, Palo Alto, California 94303 USA. This document is protected by copyright. For more information, see: http://java.sun.com/jdc/copyright.html This issue of the JDC Tech Tips is written by Glen McCluskey. JDC Tech Tips May 7, 2002 Sun, Sun Microsystems, Java, and Java Developer Connection are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. * As used in this document, the terms "Java virtual machine" or "JVM" mean a virtual machine for the Java platform. To use our one-click unsubscribe facility, select the following URL: http://bulkmail.sun.com/unsubscribe?15019588-1203339468