views:

1774

answers:

4

Should be pretty simple: I have an InputStream where I want to peek at (not read) the first two bytes, i.e. I want the "current position" of the InputStream to stil be at 0 after my peeking. What is the best and safest way to do this?

Answer - As I had suspected, the solution was to wrap it in a BufferedInputStream which offers markability. Thanks Rasmus.

+22  A: 

For a general InputStream, I would wrap it in a BufferedInputStream and do something like this:

BufferedInputStream bis = new BufferedInputStream(inputStream);
bis.mark(2);
int byte1 = bis.read();
int byte2 = bis.read();
bis.reset();
// note: you must continue using the BufferedInputStream instead of the inputStream
Rasmus Faber
See also http://java.sun.com/javase/6/docs/api/java/io/InputStream.html#markSupported()
McDowell
works like a charm, thanks!
Epaga
+2  A: 

I found an implementation of a PeekableInputStream here:

http://www.heatonresearch.com/articles/147/page2.html

The idea of the implementation shown in the article is that it keeps an array of "peeked" values internally. When you call read, the values are returned first from the peeked array, then from the input stream. When you call peek, the values are read and stored in the "peeked" array.

As the license of the sample code is LGPL, It can be attached to this post:

package com.heatonresearch.httprecipes.html;

import java.io.*;

/**
 * The Heaton Research Spider Copyright 2007 by Heaton
 * Research, Inc.
 * 
 * HTTP Programming Recipes for Java ISBN: 0-9773206-6-9
 * http://www.heatonresearch.com/articles/series/16/
 * 
 * PeekableInputStream: This is a special input stream that
 * allows the program to peek one or more characters ahead
 * in the file.
 * 
 * This class is released under the:
 * GNU Lesser General Public License (LGPL)
 * http://www.gnu.org/copyleft/lesser.html
 * 
 * @author Jeff Heaton
 * @version 1.1
 */
public class PeekableInputStream extends InputStream
{

  /**
   * The underlying stream.
   */
  private InputStream stream;

  /**
   * Bytes that have been peeked at.
   */
  private byte peekBytes[];

  /**
   * How many bytes have been peeked at.
   */
  private int peekLength;

  /**
   * The constructor accepts an InputStream to setup the
   * object.
   * 
   * @param is
   *          The InputStream to parse.
   */
  public PeekableInputStream(InputStream is)
  {
    this.stream = is;
    this.peekBytes = new byte[10];
    this.peekLength = 0;
  }

  /**
   * Peek at the next character from the stream.
   * 
   * @return The next character.
   * @throws IOException
   *           If an I/O exception occurs.
   */
  public int peek() throws IOException
  {
    return peek(0);
  }

  /**
   * Peek at a specified depth.
   * 
   * @param depth
   *          The depth to check.
   * @return The character peeked at.
   * @throws IOException
   *           If an I/O exception occurs.
   */
  public int peek(int depth) throws IOException
  {
    // does the size of the peek buffer need to be extended?
    if (this.peekBytes.length <= depth)
    {
      byte temp[] = new byte[depth + 10];
      for (int i = 0; i < this.peekBytes.length; i++)
      {
        temp[i] = this.peekBytes[i];
      }
      this.peekBytes = temp;
    }

    // does more data need to be read?
    if (depth >= this.peekLength)
    {
      int offset = this.peekLength;
      int length = (depth - this.peekLength) + 1;
      int lengthRead = this.stream.read(this.peekBytes, offset, length);

      if (lengthRead == -1)
      {
        return -1;
      }

      this.peekLength = depth + 1;
    }

    return this.peekBytes[depth];
  }

  /*
   * Read a single byte from the stream. @throws IOException
   * If an I/O exception occurs. @return The character that
   * was read from the stream.
   */
  @Override
  public int read() throws IOException
  {
    if (this.peekLength == 0)
    {
      return this.stream.read();
    }

    int result = this.peekBytes[0];
    this.peekLength--;
    for (int i = 0; i < this.peekLength; i++)
    {
      this.peekBytes[i] = this.peekBytes[i + 1];
    }

    return result;
  }

}
Mario Ortegón
+3  A: 

When using a BufferedInputStream make sure that the inputStream is not already buffered, double buffering will cause some seriously hard to find bugs. Also you need to handle Readers differently, converting to a StreamReader and Buffering will cause bytes to be lost if the Reader is Buffered. Also if you are using a Reader remember that you are not reading bytes but characters in the default encoding (unless an explicit encoding was set). An example of a buffered input stream, that you may not know is URL url; url.openStream();

I do not have any references for this information, it comes from debugging code. The main case where the issue occurred for me was in code that read from a file into a compressed stream. If I remember correctly once you start debugging through the code there are comments in the Java source that certain things do not work correctly always. I do not remember where the information from using BufferedReader and BufferedInputStream comes from but I think that fails straight away on even the simplest test. Remember to test this you need to be marking more than the buffer size (which is different for BufferedReader versus BufferedInputStream), the problems occur when the bytes being read reach the end of the buffer. Note there is a source code buffer size which can be different to the buffer size you set in the constructor. It is a while since I did this so my recollections of details may be a little off. Testing was done using a FilterReader/FilterInputStream, add one to the direct stream and one to the buffered stream to see the difference.

Donal Tobin
Interesting! Do you have any details on the problems with double buffering and with combining BufferedInputStream with InputStreamReader? I could not find anything by Googling.
Rasmus Faber
+2  A: 

You might find PushbackInputStream to be useful:

http://java.sun.com/javase/6/docs/api/java/io/PushbackInputStream.html

Alex Miller