11

I want to create an InputStream that is limited to a certain range of bytes in file, e.g. to bytes from position 0 to 100. So that the client code should see EOF once 100th byte is reached.

alex2k8
  • 42,496
  • 57
  • 170
  • 221

7 Answers7

10

The read() method of InputStream reads a single byte at a time. You could write a subclass of InputStream that maintains an internal counter; each time read() is called, update the counter. If you have hit your maximum, do not allow any further reads (return -1 or something like that).

You will also need to ensure that the other methods for reading read_int, etc are unsupported (ex: Override them and just throw UnsupportedOperationException());

I don't know what your use case is, but as a bonus you may want to implement buffering as well.

rogerdpack
  • 62,887
  • 36
  • 269
  • 388
danben
  • 80,905
  • 18
  • 123
  • 145
  • 3
    I don't think there is any need to implement buffering - that's mixing concerns, and the way read(byte[]) works, returning up to the required amount of data, makes this unnecessary. It's cleaner and equally effective to wrap the base stream in a BufferedInputStream. – mdma May 22 '10 at 20:39
  • also NB that the only methods in InputStream that are declared abstract are read() and read(byte[], int, int) so sometimes you can get away with just defining those 2 (plus maybe close()) in your child class. – rogerdpack Feb 14 '13 at 18:03
8

As danben says, just decorate your stream and enforce the constraint:

public class ConstrainedInputStream extends InputStream {
  private final InputStream decorated;
  private long length;

  public ConstrainedInputStream(InputStream decorated, long length) {
    this.decorated = decorated;
    this.length = length;
  }

  @Override public int read() throws IOException {
    return (length-- <= 0) ? -1 : decorated.read();
  }

  // TODO: override other methods if you feel it's necessary
  // optionally, extend FilterInputStream instead
}
Community
  • 1
  • 1
McDowell
  • 107,573
  • 31
  • 204
  • 267
  • 1
    Code reuse (BoundedInputStream from commons-io): http://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/input/BoundedInputStream.html – CelinHC Apr 02 '14 at 18:14
4

Consider using http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/io/LimitInputStream.html

whiskeysierra
  • 5,030
  • 1
  • 29
  • 40
  • 2
    LimitInputStream is scheduled to be removed in Guava release 15.0. Use ByteStreams.limit(java.io.InputStream, long) instead. – petertc Feb 07 '14 at 06:54
2

You can use guava's ByteStreams. Notice that you should use skipFully() before limit, for example:

ByteStreams.skipFully(tmpStream, range.start());
tmpStream = ByteStreams.limit(tmpStream, range.length());
petertc
  • 3,607
  • 1
  • 31
  • 36
2

If you only need 100 bytes, then simple is probably best, I'd read them into an array and wrap that as a ByteArrayInputStream. E.g.

   int length = 100;
   byte[] data = new byte[length];
   InputStream in = ...;  //your inputstream
   DataInputStream din = new DataInputStream(din);
   din.readFully(data);
   ByteArrayInputStream first100Bytes = new ByteArrayInputStream(data);
   // pass first100bytes to your clients

If you don't want to use DataInputStream.readFully, there is IOUtils.readFully from apache commons-io, or you can implment the read loop explicitly.

If you have more advanced needs, such as reading from a segment in the middle of the file, or larger amounts of data, then extending InputStream and overriding the read(byte[], int,int) as well as read(), will give you better performance than just overriding the read() method.

mdma
  • 56,943
  • 12
  • 94
  • 128
2

In addition to this solution, using the skip method of an InputStream, you can also read a range starting in the middle of the file.

public class RangeInputStream extends InputStream
{
    private InputStream parent;
    private long remaining;

    public RangeInputStream(InputStream parent, long start, long end) throws IOException
    {
        if (end < start)
        {
            throw new IllegalArgumentException("end < start");
        }

        if (parent.skip(start) < start)
        {
            throw new IOException("Unable to skip leading bytes");
        }

        this.parent=parent;
        remaining = end - start;
    }

    @Override
    public int read() throws IOException
    {
        return --remaining >= 0 ? parent.read() : -1;
    }
}
Community
  • 1
  • 1
Ercksen
  • 668
  • 9
  • 23
0

I was solved a similar problem for my project, you can see the working code here PartInputStream. I was used it for assets and files input streams. But it is not suitable for а streams whose length is not available initially, such as network streams.

lexa-b
  • 1,759
  • 15
  • 15