Loading ...
Sorry, an error occurred while loading the content.

JDC Tech Tips, May 7, 2002 (File Channels, Stack Trace Elements)

Expand Messages
  • Magno Cavalcante
    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
    Message 1 of 1 , May 10, 2002
    • 0 Attachment
      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 <stdio.h>
      #include <assert.h>

      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 = <init>
      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 <init>, 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@...

      * 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.
    Your message has been successfully submitted and would be delivered to recipients shortly.