Thursday 23 March 2017

The Byte Streams - Java Tutorials ( Page 2 of 2 )

ByteArrayOutputStream

ByteArrayOutputStream is an implementation of an output stream that uses a byte array as the destination. ByteArrayOutputStream has two constructors, shown here:

      ByteArrayOutputStream( )
      ByteArrayOutputStream(int numBytes)

In the first form, a buffer of 32 bytes is created. In the second, a buffer is created with a size equal to that specified by numBytes. The buffer is held in the protected buf field of ByteArrayOutputStream. The buffer size will be increased automatically, if needed. The number of bytes held by the buffer is contained in the protected count field of ByteArrayOutputStream.

The following example demonstrates ByteArrayOutputStream:

  // Demonstrate ByteArrayOutputStream.
  import java.io.*;

  class ByteArrayOutputStreamDemo {
    public static void main(String args[]) throws IOException {
      ByteArrayOutputStream f = new ByteArrayOutputStream();
      String s = "This should end up in the array";
      byte buf[] = s.getBytes();

      f.write(buf);
      System.out.println("Buffer as a string");
      System.out.println(f.toString());
      System.out.println("Into array");
      byte b[] = f.toByteArray();
      for (int i=0; i<b.length; i++) {
        System.out.print((char) b[i]);
      }
      System.out.println("\nTo an OutputStream()");
      OutputStream f2 = new FileOutputStream("test.txt");

      f.writeTo(f2);
      f2.close();
      System.out.println("Doing a reset");
      f.reset();
      for (int i=0; i<3; i++)
        f.write('X');
      System.out.println(f.toString());
    }
  }

When you run the program, you will create the following output. Notice how after the
call to reset( ), the three X’s end up at the beginning.

  Buffer as a string
  This should end up in the array
  Into array
  This should end up in the array
  To an OutputStream()
  Doing a reset
  XXX

This example uses the writeTo( ) convenience method to write the contents of f to test.txt. Examining the contents of the test.txt file created in the preceding example shows the result we expected:

  This should end up in the array


Filtered Byte Streams

Filtered streams are simply wrappers around underlying input or output streams that transparently provide some extended level of functionality. These streams are typically accessed by methods that are expecting a generic stream, which is a superclass of the filtered streams. Typical extensions are buffering, character translation, and raw data translation. The filtered byte streams are FilterInputStream and FilterOutputStream. Their constructors are shown here:

      FilterOutputStream(OutputStream os)
      FilterInputStream(InputStream is)

The methods provided in these classes are identical to those in InputStream and OutputStream.


Buffered Byte Streams

For the byte-oriented streams, a buffered stream extends a filtered stream class by attaching a memory buffer to the I/O streams. This buffer allows Java to do I/O operations on more than a byte at a time, hence increasing performance. Because the buffer is available, skipping, marking, and resetting of the stream becomes possible. The buffered byte stream classes are BufferedInputStream and BufferedOutputStream. PushbackInputStream also implements a buffered stream.


BufferedInputStream

Buffering I/O is a very common performance optimization. Java’s BufferedInputStream class allows you to “wrap” any InputStream into a buffered stream and achieve this performance improvement.

BufferedInputStream has two constructors:

      BufferedInputStream(InputStream inputStream)
      BufferedInputStream(InputStream inputStream, int bufSize)

The first form creates a buffered stream using a default buffer size. In the second, the size of the buffer is passed in bufSize. Use of sizes that are multiples of memory page, disk block, and so on can have a significant positive impact on performance. This is, however, implementation-dependent. An optimal buffer size is generally dependent on the host operating system, the amount of memory available, and how the machine is configured. To make good use of buffering doesn’t necessarily require quite this degree of sophistication. A good guess for a size is around 8,192 bytes, and attaching even a rather small buffer to an I/O stream is always a good idea. That way, the low-level system can read blocks of data from the disk or network and store the results in your buffer. Thus, even if you are reading the data a byte at a time out of the InputStream, you will be manipulating fast memory over 99.9 percent of the time.

Buffering an input stream also provides the foundation required to support moving backward in the stream of the available buffer. Beyond the read( ) and skip( ) methods implemented in any InputStream, BufferedInputStream also supports the mark( ) and reset( ) methods. This support is reflected by BufferedInputStream.markSupported( ) returning true.

The following example contrives a situation where we can use mark( ) to remember where we are in an input stream and later use reset( ) to get back there. This example is parsing a stream for the HTML entity reference for the copyright symbol. Such a reference begins with an ampersand (&) and ends with a semicolon (;) without any intervening whitespace. The sample input has two ampersands to show the case where the reset( ) happens and where it does not.

  // Use buffered input.
  import java.io.*;

  class BufferedInputStreamDemo {
    public static void main(String args[]) throws IOException {
      String s = "This is a &copy; copyright symbol " +
        "but this is &copy not.\n";
      byte buf[] = s.getBytes();
      ByteArrayInputStream in = new ByteArrayInputStream(buf);
      BufferedInputStream f = new BufferedInputStream(in);
      int c;
      boolean marked = false;

      while ((c = f.read()) != -1) {
        switch(c) {
        case '&':
          if (!marked) {
            f.mark(32);
            marked = true;
          } else {
            marked = false;
          }
          break;
        case ';':
          if (marked) {
            marked = false;
            System.out.print("(c)");
          } else
            System.out.print((char) c);
          break;
        case ' ':
          if (marked) {
            marked = false;
            f.reset();
            System.out.print("&");
          } else
            System.out.print((char) c);
          break;
        default:
          if (!marked)
              System.out.print((char) c);
          break;
        }
      }
    }
  }

Notice that this example uses mark(32), which preserves the mark for the next 32 bytes read (which is enough for all entity references). Here is the output produced by this program:

  This is a (c) copyright symbol but this is &copy not.

Use of mark( ) is restricted to access within the buffer. This means that you can only specify a parameter to mark( ) that is smaller than the buffer size of the stream.


BufferedOutputStream

A BufferedOutputStream is similar to any OutputStream with the exception of an added flush( ) method that is used to ensure that data buffers are physically written to the actual output device. Since the point of a BufferedOutputStream is to improve performance by reducing the number of times the system actually writes data, you may need to call flush( ) to cause any data that is in the buffer to get written.

Unlike buffered input, buffering output does not provide additional functionality. Buffers for output in Java are there to increase performance. Here are the two available constructors:

      BufferedOutputStream(OutputStream outputStream)
      BufferedOutputStream(OutputStream outputStream, int bufSize)

The first form creates a buffered stream using a buffer of 512 bytes. In the second form, the size of the buffer is passed in bufSize.


PushbackInputStream

One of the novel uses of buffering is the implementation of pushback. Pushback is used on an input stream to allow a byte to be read and then returned (that is, “pushed back”) to the stream. The PushbackInputStream class implements this idea. It provides a mechanism to “peek” at what is coming from an input stream without disrupting it.

PushbackInputStream has the following constructors:

      PushbackInputStream(InputStream inputStream)
      PushbackInputStream(InputStream inputStream, int numBytes)

The first form creates a stream object that allows one byte to be returned to the input tream. The second form creates a stream that has a pushback buffer that is numBytes long. This allows multiple bytes to be returned to the input stream.

Beyond the familiar methods of InputStream, PushbackInputStream provides unread( ), shown here:

      void unread(int ch)
      void unread(byte buffer[ ])
      void unread(byte buffer, int offset, int numChars)

The first form pushes back the low-order byte of ch. This will be the next byte returned by a subsequent call to read( ). The second form returns the bytes in buffer. The third form pushes back numChars bytes beginning at offset from buffer. An IOException will be thrown if there is an attempt to return a byte when the pushback buffer is full.

Java 2 made a small change to PushbackInputStream: it added the skip( ) method. Here is an example that shows how a programming language parser might use a PushbackInputStream and unread( ) to deal with the difference between the = = operator for comparison and the = operator for assignment:

  // Demonstrate unread().
  import java.io.*;

  class PushbackInputStreamDemo {
    public static void main(String args[]) throws IOException {
      String s = "if (a == 4) a = 0;\n";
      byte buf[] = s.getBytes();
      ByteArrayInputStream in = new ByteArrayInputStream(buf);
      PushbackInputStream f = new PushbackInputStream(in);
      int c;

      while ((c = f.read()) != -1) {
        switch(c) {
        case '=':
        if ((c = f.read()) == '=')
          System.out.print(".eq.");
        else {
          System.out.print("<-");
          f.unread(c);
        }
        break;
      default:
        System.out.print((char) c);
        break;
        }
      }
    }
  }

Here is the output for this example. Notice that = = was replaced by “.eq.” and = was replaced by “<–”.

  if (a .eq. 4) a <- 0;
  
PushbackInputStream has the side effect of invalidating the mark( ) or reset( ) methods of the InputStream used to create it. Use markSupported( ) to check any stream on which you are going to use mark( )/reset( ).


SequenceInputStream

The SequenceInputStream class allows you to concatenate multiple InputStreams. The construction of a SequenceInputStream is different from any other InputStream. A SequenceInputStream constructor uses either a pair of InputStreams or an Enumeration of InputStreams as its argument:

      SequenceInputStream(InputStream first, InputStream second)
      SequenceInputStream(Enumeration streamEnum)

Operationally, the class fulfills read requests from the first InputStream until it runs out and then switches over to the second one. In the case of an Enumeration, it will continue through all of the InputStreams until the end of the last one is reached.

Here is a simple example that uses a SequenceInputStream to output the contents of two files:

  // Demonstrate sequenced input.
  import java.io.*;
  import java.util.*;

  class InputStreamEnumerator implements Enumeration {
    private Enumeration files;
    public InputStreamEnumerator(Vector files) {
      this.files = files.elements();
    }

    public boolean hasMoreElements() {
      return files.hasMoreElements();
    }

    public Object nextElement() {
      try {
        return new FileInputStream(files.nextElement().toString());
      } catch (Exception e) {
        return null;
      }
    }
  }

  class SequenceInputStreamDemo {
    public static void main(String args[]) throws Exception {
      int c;
      Vector files = new Vector();

      files.addElement("/autoexec.bat");
      files.addElement("/config.sys");
      InputStreamEnumerator e = new InputStreamEnumerator(files);
      InputStream input = new SequenceInputStream(e);

      while ((c = input.read()) != -1) {
        System.out.print((char) c);
      }
      input.close();
    }
  }

This example creates a Vector and then adds two filenames to it. It passes that vector of names to the InputStreamEnumerator class, which is designed to provide a wrapper on the vector where the elements returned are not the filenames but rather open FileInputStreams on those names. The SequenceInputStream opens each file in turn, and this example prints the contents of the two files.


PrintStream

The PrintStream class provides all of the formatting capabilities we have been using from the System file handle, System.out, since the beginning of the book. Here are two of its constructors:

      PrintStream(OutputStream outputStream)
      PrintStream(OutputStream outputStream, boolean flushOnNewline)

where flushOnNewline controls whether Java flushes the output stream every time a newline (\n) character is output. If flushOnNewline is true, flushing automatically takes place. If it is false, flushing is not automatic. The first constructor does not automatically flush.

Java’s PrintStream objects support the print( ) and println( ) methods for all types, including Object. If an argument is not a simple type, the PrintStream methods will call the object’s toString( ) method and then print the result.


RandomAccessFile

RandomAccessFile encapsulates a random-access file. It is not derived from InputStream or OutputStream. Instead, it implements the interfaces DataInput and DataOutput, which define the basic I/O methods. It also supports positioning requests—that is, you can position the file pointer within the file. It has these two constructors:

      RandomAccessFile(File fileObj, String access)
          throws FileNotFoundException

      RandomAccessFile(String filename, String access)
          throws FileNotFoundException

In the first form, fileObj specifies the name of the file to open as a File object. In the second form, the name of the file is passed in filename. In both cases, access determines what type of file access is permitted. If it is “r”, then the file can be read, but not written. If it is “rw”, then the file is opened in read-write mode. If it is “rws”, the file is opened for read-write operations and every change to the file’s data or metadata will be immediately written to the physical device. If it is “rwd”, the file is opened for read-write operations and every change to the file’s data will be immediately written to the physical device.

The method seek( ), shown here, is used to set the current position of the file pointer within the file:

      void seek(long newPos) throws IOException

Here, newPos specifies the new position, in bytes, of the file pointer from the beginning of the file. After a call to seek( ), the next read or write operation will occur at the new file position.

RandomAccessFile implements the standard input and output methods, which you can use to read and write to random access files. It also includes some additional methods. One is setLength( ). It has this signature:

      void setLength(long len) throws IOException

This method sets the length of the invoking file to that specified by len. This method can be used to lengthen or shorten a file. If the file is lengthened, the added portion is undefined. Java 2, version 1.4 added the getChannel( ) method to RandomAccessFile. This method returns a channel connected to the RandomAccessFile object. Channels are used by the new I/O classes contained in java.nio.

No comments:

Post a Comment