src.it.unimi.dsi.io.InputBitStream Maven / Gradle / Ivy
package it.unimi.dsi.io;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.Flushable;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.nio.channels.FileChannel;
/*
* DSI utilities
*
* Copyright (C) 2002-2017 Sebastiano Vigna
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see .
*
*/
import it.unimi.dsi.bits.Fast;
import it.unimi.dsi.fastutil.booleans.BooleanIterator;
import it.unimi.dsi.fastutil.ints.IntIterators;
import it.unimi.dsi.fastutil.io.BinIO;
import it.unimi.dsi.fastutil.io.FastBufferedInputStream;
import it.unimi.dsi.fastutil.io.RepositionableStream;
/** Bit-level input stream.
*
* This class wraps any {@link InputStream} so that you can treat it as
* bit stream. Constructors and methods closely resemble those of
* {@link InputStream}. Data can be read from such a stream in several ways:
* reading a (long) natural number in fixed-width, unary, γ, shifted γ, δ, ζ and (skewed)
* Golomb coding, or reading a number of bits that will be stored in a vector of
* bytes. There is limited support for {@link #mark(int)}/{@link #reset()}
* operations.
*
*
This class can also {@linkplain #InputBitStream(byte[]) wrap a byte
* array}; this is much more lightweight than wrapping a {@link
* it.unimi.dsi.fastutil.io.FastByteArrayInputStream} wrapping the array. Overflowing the array
* will cause an {@link java.io.EOFException}.
*
*
Note that when reading using a vector of bytes bits are read in the
* stream format (see {@link OutputBitStream}): the first bit is bit 7 of the
* first byte, the eighth bit is bit 0 of the first byte, the ninth bit is bit
* 7 of the second byte and so on. When reading natural numbers using some coding,
* instead, they are stored in the standard way, that is, in the lower
* bits.
*
*
Additional features:
*
*
*
* - This class provides an internal buffer. By setting a buffer of
* length 0 at creation time, you can actually bypass the buffering system:
* Note, however, that several classes providing buffering have synchronised
* methods, so using a wrapper instead of the internal buffer is likely to lead
* to a performance drop.
*
*
- To work around the schizophrenic relationship between streams and random
* access files in {@link java.io}, this class provides a {@link #flush()}
* method that resets the internal state. At this point, you can safely reposition
* the underlying stream and read again afterwards. For instance, this is safe
* and will perform as expected:
*
* FileInputStream fis = new FileInputStream(...);
* InputBitStream ibs = new InputBitStream(fis);
* ... read operations on ibs ...
* ibs.flush();
* fis.getChannel().position(...);
* ... other read operations on ibs ...
*
*
* As a commodity, an instance of this class will try to cast the underlying byte
* stream to a {@link RepositionableStream} and to fetch by reflection the {@link
* java.nio.channels.FileChannel} underlying the given input stream, in this
* order. If either reference can be successfully fetched, you can use
* directly the {@link #position(long) position()} method with argument
* pos
with the same semantics of a {@link #flush()}, followed by
* a call to position(pos / 8)
(where the latter method belongs
* either to the underlying stream or to its underlying file channel), followed
* by a {@link #skip(long) skip(pos % 8)}. However, since the reflective checks are quite
* heavy they can be disabled using a {@linkplain InputBitStream#InputBitStream(InputStream, boolean) suitable constructor}.
*
*
- Finally, this class implements partially the interface of a boolean iterator.
* More precisely, {@link #nextBoolean()} will return the same bit as {@link #readBit()},
* and also the same exceptions, whereas {@link #hasNext()} will always return true:
* you must be prepared to catch a {@link java.lang.RuntimeException} wrapping an {@link IOException}
* in case the file ends. It
* is very difficult to implement completely an eager operator using a input-stream
* based model.
*
*
*
* This class is not synchronised. If multiple threads
* access an instance of this class concurrently, they must be synchronised externally.
*
* @see java.io.InputStream
* @see it.unimi.dsi.io.OutputBitStream
* @author Sebastiano Vigna
* @since 0.1
*/
public class InputBitStream implements BooleanIterator, Flushable, Closeable {
private final static boolean DEBUG = false;
/* Precomputed tables: the i-th entry decodes the stream fragment of 16 bits given by the binary reprentation of i.
* The upper 16 bits contain code lengths, the lower 16 bits decoded values. 0 means undecodable. */
public final static int[] GAMMA = new int[256 * 256], DELTA = new int[256 * 256], ZETA_3 = new int[256 * 256], SHIFTED_GAMMA = new int[256 * 256];
static void fillArrayFromResource(final String resource, final int array[]) throws IOException {
final String resouceFullPath = "/it/unimi/dsi/io/" + resource;
final InputStream ris = InputBitStream.class.getResourceAsStream(resouceFullPath);
if (ris == null) throw new IOException("Cannot open resource " + resouceFullPath);
DataInputStream dis = new DataInputStream(new FastBufferedInputStream(ris));
BinIO.loadInts(dis, array, 0, array.length);
dis.close();
assert checkLength(resource, array, resouceFullPath);
}
public static boolean checkLength(final String resource, final int[] array, final String resouceFullPath) {
final int actualLength = IntIterators.unwrap(BinIO.asIntIterator(new DataInputStream(InputBitStream.class.getResourceAsStream(resouceFullPath)))).length;
assert array.length == actualLength : resource + " is long " + actualLength + " but we think it should rather be " + array.length;
return true;
}
static {
/* We load all precomputed arrays from resource files,
* to work around the limit on static initialiser code. */
try {
fillArrayFromResource("gamma.in.16", GAMMA);
fillArrayFromResource("delta.in.16", DELTA);
fillArrayFromResource("zeta3.in.16", ZETA_3);
fillArrayFromResource("shiftedgamma.in.16", SHIFTED_GAMMA);
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
/** The default size of the byte buffer in bytes (8Ki). */
public final static int DEFAULT_BUFFER_SIZE = 8 * 1024;
/** The underlying {@link InputStream}. */
protected final InputStream is;
/** Whether we should use the byte buffer. */
private final boolean noBuffer;
/** The cached file channel underlying {@link #is}, if any. */
protected final FileChannel fileChannel;
/** {@link #is} cast to a positionable stream, if possible. */
protected final RepositionableStream repositionableStream;
/** True if we are wrapping an array. */
protected final boolean wrapping;
/** The number of bits actually read from this bit stream. */
private long readBits;
/** Current bit buffer: the lowest {@link #fill} bits represent the current content (the remaining bits are undefined). */
private int current;
/** The stream buffer. */
protected byte[] buffer;
/** Current number of bits in the bit buffer (stored low). */
protected int fill;
/** Current position in the byte buffer. */
protected int pos;
/** Current number of bytes available in the byte buffer. */
protected int avail;
/** Current position of the first byte in the byte buffer. */
protected long position;
/** This (non-public) constructor exists just to provide fake initialisation for classes such as {@link DebugInputBitStream}.
*/
protected InputBitStream() {
is = null;
noBuffer = true;
repositionableStream = null;
fileChannel = null;
wrapping = false;
}
/** Creates a new input bit stream wrapping a given input stream using a buffer of size {@link #DEFAULT_BUFFER_SIZE}.
*
*
This constructor performs the reflective tests that are necessary to support {@link #position(long)}.
*
* @param is the input stream to wrap.
*/
public InputBitStream(final InputStream is) {
this(is, true);
}
/** Creates a new input bit stream wrapping a given input stream using a buffer of size {@link #DEFAULT_BUFFER_SIZE}.
*
* @param is the input stream to wrap.
* @param testForPosition if false, the reflective test that is necessary to support {@link #position(long)}
* in case is
does not implement {@link RepositionableStream} will not be performed.
*/
public InputBitStream(final InputStream is, final boolean testForPosition) {
this(is, DEFAULT_BUFFER_SIZE, testForPosition);
}
/** Creates a new input bit stream wrapping a given input stream with a specified buffer size.
*
*
This constructor performs the reflective tests that are necessary to support {@link #position(long)}.
*
* @param is the input stream to wrap.
* @param bufSize the size in byte of the buffer; it may be 0, denoting no buffering.
*/
public InputBitStream(final InputStream is, final int bufSize) {
this(is, bufSize, true);
}
/** Creates a new input bit stream wrapping a given input stream with a specified buffer size.
*
* @param is the input stream to wrap.
* @param bufSize the size in byte of the buffer; it may be 0, denoting no buffering.
* @param testForPosition if false, the reflective test that is necessary to support {@link #position(long)}
* in case is
does not implement {@link RepositionableStream} will not be performed.
*/
public InputBitStream(final InputStream is, final int bufSize, final boolean testForPosition) {
this.is = is;
wrapping = false;
if (! (this.noBuffer = bufSize == 0)) this.buffer = new byte[bufSize];
// Cheap test, we do it all the time
if (is instanceof RepositionableStream) {
repositionableStream = (RepositionableStream)is;
fileChannel = null;
}
else if (testForPosition) {
FileChannel fc = null;
try {
fc = (FileChannel)(is.getClass().getMethod("getChannel")).invoke(is);
}
catch(IllegalAccessException e) {}
catch(IllegalArgumentException e) {}
catch(NoSuchMethodException e) {}
catch(InvocationTargetException e) {}
catch(ClassCastException e) {}
fileChannel = fc;
repositionableStream = null;
}
else {
repositionableStream = null;
fileChannel = null;
}
}
/** Creates a new input bit stream wrapping a given file input stream using a buffer of size {@link #DEFAULT_BUFFER_SIZE}.
*
*
This constructor invokes directly {@link FileInputStream#getChannel()} to support {@link #position(long)}.
*
* @param is the file input stream to wrap.
*/
public InputBitStream(final FileInputStream is) {
this(is, DEFAULT_BUFFER_SIZE);
}
/** Creates a new input bit stream wrapping a given file input stream with a specified buffer size.
*
*
This constructor invokes directly {@link FileInputStream#getChannel()} to support {@link #position(long)}.
*
* @param is the file input stream to wrap.
* @param bufSize the size in byte of the buffer; it may be 0, denoting no buffering.
*/
public InputBitStream(final FileInputStream is, final int bufSize) {
this.is = is;
wrapping = false;
if (! (this.noBuffer = bufSize == 0)) this.buffer = new byte[bufSize];
repositionableStream = null;
fileChannel = is.getChannel();
}
/** Creates a new input bit stream wrapping a given byte array.
*
* @param a the byte array to wrap.
*/
public InputBitStream(final byte[] a) {
is = NullInputStream.getInstance();
repositionableStream = null;
fileChannel = null;
if (a.length > 0) {
buffer = a;
avail = a.length;
wrapping = true;
noBuffer = false;
}
else {
// A zero-length buffer is like having no buffer
buffer = null;
avail = 0;
wrapping = false;
noBuffer = true;
}
}
/** Creates a new input bit stream reading from a file.
*
*
This constructor invokes directly {@link FileInputStream#getChannel()} to support {@link #position(long)}.
*
* @param name the name of the file.
* @param bufSize the size in byte of the buffer; it may be 0, denoting no buffering.
*/
public InputBitStream(final String name, final int bufSize) throws FileNotFoundException {
this(new FileInputStream(name), bufSize);
}
/** Creates a new input bit stream reading from a file.
*
*
This constructor invokes directly {@link FileInputStream#getChannel()} to support {@link #position(long)}.
*
* @param name the name of the file.
*/
public InputBitStream(final String name) throws FileNotFoundException {
this(new FileInputStream(name), DEFAULT_BUFFER_SIZE);
}
/** Creates a new input bit stream reading from a file.
*
*
This constructor invokes directly {@link FileInputStream#getChannel()} to support {@link #position(long)}.
*
* @param file the file.
*/
public InputBitStream(final File file) throws FileNotFoundException {
this(new FileInputStream(file), DEFAULT_BUFFER_SIZE);
}
/** Creates a new input bit stream reading from a file.
*
*
This constructor invokes directly {@link FileInputStream#getChannel()} to support {@link #position(long)}.
*
* @param file the file.
* @param bufSize the size in byte of the buffer; it may be 0, denoting no buffering.
*/
public InputBitStream(final File file, final int bufSize) throws FileNotFoundException {
this(new FileInputStream(file), bufSize);
}
/** Flushes the bit stream. All state information associated with the stream is reset. This
* includes bytes prefetched from the stream, bits in the bit buffer and unget'd bits.
*
*
This method is provided so that users of this class can easily wrap repositionable
* streams (for instance, file-based streams, which can be repositioned using
* the underlying {@link java.nio.channels.FileChannel}). It is guaranteed that after calling
* this method the underlying stream can be repositioned, and that the next read
* will draw data from the stream.
*/
@Override
public void flush() {
if (! wrapping) {
position += pos;
avail = 0;
pos = 0;
}
fill = 0;
}
/** Closes the bit stream. All resources associated with the stream are released.
*/
@Override
public void close() throws IOException {
if (is != null && is != System.in) is.close();
buffer = null;
}
/** Returns the number of bits that can be read (or skipped over) from this
* bit stream without blocking by the next caller of a method.
*
* @return the number of bits that can be read from this bit stream without blocking.
*/
public long available() throws IOException {
return (is.available() + avail) * 8 + fill;
}
/** Returns the number of bits read from this bit stream.
*
* @return the number of bits read so far.
*/
public long readBits() {
return readBits;
}
/** Sets the number of bits read from this bit stream.
*
*
This method is provided so that, for instance, the
* user can reset via readBits(0)
the read-bits count
* after a {@link #flush()}.
*
* @param readBits the new value for the number of bits read so far.
*/
public void readBits(final long readBits) {
this.readBits = readBits;
}
/** Reads the next byte from the stream.
*
*
This method takes care of managing the buffering logic
* transparently.
*
*
However, this method does not update {@link #readBits}.
* The caller should increment {@link #readBits} by 8 at each call, unless
* the bit are used to load {@link #current}.
*/
private final int read() throws IOException {
if (noBuffer) {
final int t = is.read();
if (t == -1) throw new EOFException();
else position++;
return t;
}
if (avail == 0) {
avail = is.read(buffer);
if (avail == -1) {
avail = 0;
throw new EOFException();
}
else {
position += pos;
pos = 0;
}
}
avail--;
return buffer[pos++] & 0xFF;
}
/** Feeds 16 more bits into {@link #current}, assuming that {@link #fill} is less than 16.
*
*
This method will never throw an {@link EOFException}—simply, it will refill less than 16 bits.
*
* @return {@link #fill}.
*/
private final int refill() throws IOException {
assert fill < 16;
if (avail > 1) {
// If there is a byte in the buffer, we use it directly.
avail -= 2;
current = current << 16 | (buffer[pos++] & 0xFF) << 8 | buffer[pos++] & 0xFF;
return fill += 16;
}
try{
current = (current << 8) | read();
fill += 8;
current = (current << 8) | read();
fill += 8;
}
catch(EOFException dontCare) {}
return fill;
}
/** Reads bits from the bit buffer, possibly refilling it.
*
*
This method is the basic mean for extracting bits from the underlying stream.
*
*
You cannot read more than {@link #fill} bits with this method (unless {@link #fill} is 0,
* and len
is nonzero, in which case the buffer will be refilled for you with 8 bits), and if you
* read exactly {@link #fill} bits the buffer will be empty afterwards. In particular,
* there will never be 8 bits in the buffer.
*
*
The bit buffer stores its content in the lower {@link #fill} bits. The content
* of the remaining bits is undefined.
*
*
This method updates {@link #readBits}.
*
* @param len the number of bits to read.
* @return the bits read (in the lower positions).
* @throws AssertionError if one tries to read more bits than available in the buffer and assertions are enabled.
*/
private final int readFromCurrent(final int len) throws IOException {
if (len == 0) return 0;
if (fill == 0) {
current = read();
fill = 8;
}
assert len <= fill : len + " bit(s) requested, " + fill + " available";
readBits += len;
return current >>> (fill -= len) & (1 << len) - 1;
}
/** Aligns the stream.
*
* After a call to this function, the stream is byte aligned. Bits that have been
* read to align are discarded.
*/
public void align() {
if ((fill & 7) == 0) return;
readBits += fill & 7;
fill &= ~7;
}
/** Reads a sequence of bits.
*
* Bits will be read in the natural way: the first bit is bit 7 of the
* first byte, the eightth bit is bit 0 of the first byte, the ninth bit is
* bit 7 of the second byte and so on.
*
* @param bits an array of bytes to store the result.
* @param len the number of bits to read.
*/
public void read(final byte[] bits, int len) throws IOException {
assert fill < 32 : fill + " >= " + 32;
if (len <= fill) {
if (len <= 8) {
bits[0] = (byte)(readFromCurrent(len) << 8 - len);
return;
}
else if (len <= 16){
bits[0] = (byte)(readFromCurrent(8));
bits[1] = (byte)(readFromCurrent(len - 8) << 16 - len);
return;
}
else if (len <= 24) {
bits[0] = (byte)(readFromCurrent(8));
bits[1] = (byte)(readFromCurrent(8));
bits[2] = (byte)(readFromCurrent(len - 16) << 24 - len);
return;
}
else {
bits[0] = (byte)(readFromCurrent(8));
bits[1] = (byte)(readFromCurrent(8));
bits[2] = (byte)(readFromCurrent(8));
bits[3] = (byte)(readFromCurrent(len - 24) << 32 - len);
return;
}
}
else {
int i, j = 0, b;
if (fill >= 24) {
bits[j++] = (byte)(readFromCurrent(8));
bits[j++] = (byte)(readFromCurrent(8));
bits[j++] = (byte)(readFromCurrent(8));
len -= 24;
}
else if (fill >= 16) {
bits[j++] = (byte)(readFromCurrent(8));
bits[j++] = (byte)(readFromCurrent(8));
len -= 16;
}
else if (fill >= 8) {
bits[j++] = (byte)(readFromCurrent(8));
len -= 8;
}
final int shift = fill;
if (shift != 0) {
bits[j] = (byte)(readFromCurrent(shift) << 8 - shift);
len -= shift;
i = len >> 3;
while(i-- != 0) {
b = read();
bits[j] |= (b & 0xFF) >>> shift;
bits[++j] = (byte)(b << 8 - shift);
}
}
else {
i = len >> 3;
while(i-- != 0) bits[j++] = (byte)read();
}
readBits += len & ~7;
len &= 7;
if (len != 0) {
if (shift == 0) bits[j] = 0; // We must zero the next byte before OR'ing stuff in
if (len <= 8 - shift) {
bits[j] |= (byte)(readFromCurrent(len) << 8 - shift - len);
}
else {
bits[j] |= (byte)(readFromCurrent(8 - shift));
bits[j + 1] = (byte)(readFromCurrent(len + shift - 8) << 16 - shift - len);
}
}
}
}
/** Reads a bit.
*
* @return the next bit from the stream.
*/
public int readBit() throws IOException {
return readFromCurrent(1);
}
/** Reads a fixed number of bits into an integer.
*
* @param len a bit length.
* @return an integer whose lower len
bits are taken from the stream; the rest is zeroed.
*/
public int readInt(int len) throws IOException {
int i, x = 0;
if (len < 0 || len > 32) throw new IllegalArgumentException("You cannot read " + len + " bits into an integer.");
if (fill < 16) refill();
if (len <= fill) return readFromCurrent(len);
len -= fill;
x = readFromCurrent(fill);
i = len >> 3;
while(i-- != 0) x = x << 8 | read();
readBits += len & ~7;
len &= 7;
return (x << len) | readFromCurrent(len);
}
/** Reads a fixed number of bits into a long.
*
* @param len a bit length.
* @return a long whose lower len
bits are taken from the stream; the rest is zeroed.
*/
public long readLong(int len) throws IOException {
int i;
long x = 0;
if (len < 0 || len > 64) throw new IllegalArgumentException("You cannot read " + len + " bits into a long.");
if (fill < 16) refill();
if (len <= fill) return readFromCurrent(len);
len -= fill;
x = readFromCurrent(fill);
i = len >> 3;
while(i-- != 0) x = x << 8 | read();
readBits += len & ~7;
len &= 7;
return (x << len) | readFromCurrent(len);
}
/** Skips the given number of bits.
*
* @param n the number of bits to skip.
* @return the actual number of skipped bits.
*/
public long skip(long n) throws IOException {
if (n <= fill) {
if (n < 0) throw new IllegalArgumentException("Negative bit skip value: " + n);
fill -= n;
readBits += n;
return n;
}
else {
final long prevReadBits = readBits;
n -= fill;
readBits += fill;
fill = 0;
long nb = n >> 3;
// TODO: A real evaluation of the usefulness of this block of code
if (buffer != null && nb > avail && nb < avail + buffer.length) {
/* If we can skip by simply filling the buffer and skipping some bytes,
we do it. Usually the next block has already been fetched by a read-ahead logic. */
readBits += (avail + 1) << 3;
n -= (avail + 1) << 3;
nb -= avail + 1;
position += pos + avail;
pos = avail = 0;
read();
}
if (nb <= avail) {
// We skip bytes directly inside the buffer.
pos += (int)nb;
avail -= (int)nb;
readBits += n & ~7;
}
else {
// No way, we have to pass the byte skip to the underlying stream.
n -= avail << 3;
readBits += avail << 3;
final long toSkip = nb - avail;
// ALERT: the semantics of skip is flawed--this should be somehow fixed.
final long skipped = is.skip(toSkip);
if (skipped < toSkip) throw new IOException("skip() has skipped " + skipped + " instead of " + toSkip + " bytes");
position += (avail + pos) + skipped;
pos = 0;
avail = 0;
readBits += skipped << 3;
if (skipped != toSkip) return readBits - prevReadBits;
}
final int residual = (int)(n & 7);
if (residual != 0) {
current = read();
fill = 8 - residual;
readBits += residual;
}
return readBits - prevReadBits;
}
}
/** Sets this stream bit position, if it is based on a {@link RepositionableStream} or on a {@link java.nio.channels.FileChannel}.
*
*
Given an underlying stream that implements {@link
* RepositionableStream} or that can provide a {@link
* java.nio.channels.FileChannel} via the getChannel()
method,
* a call to this method has the same semantics of a {@link #flush()},
* followed by a call to {@link
* java.nio.channels.FileChannel#position(long) position(position / 8)} on
* the byte stream, followed by a {@link #skip(long) skip(position % 8)}.
*
*
Note that this method does not change the value returned by {@link #readBits()}.
*
* @param position the new position expressed as a bit offset.
* @throws UnsupportedOperationException if the underlying byte stream does not implement
* {@link RepositionableStream} or if the channel it returns is not a {@link java.nio.channels.FileChannel}.
* @see FileChannel#position(long)
*/
public void position(final long position) throws IOException {
if (DEBUG) System.err.println(this + ".position(" + position + ")");
if (position < 0) throw new IllegalArgumentException("Illegal position: " + position);
final long bitDelta = ((this.position + pos) << 3) - position;
if (bitDelta >= 0 && bitDelta <= fill) {
if (DEBUG) System.err.println("Bit positioning... position: " + position + " this.position: " + this.position + " pos: " + pos + " bitDelta: " + bitDelta + " fill: " + fill);
fill = (int)bitDelta;
//System.err.println("Post: " + position + " fill: " + fill);
return;
}
final long delta = (position >> 3) - (this.position + pos);
if (DEBUG) System.err.println(this + ".position(" + position + "); curr: " + this.position + " delta: " + delta + " pos: " + pos + " avail: " + avail);
if (delta <= avail && delta >= - pos) {
// We can reposition just by moving into the buffer.
avail -= delta;
pos += delta;
fill = 0;
if (DEBUG) System.err.println(this + ": moved internally; pos: " + pos + " avail: " + avail);
}
else if (repositionableStream != null) {
flush();
repositionableStream.position(this.position = position >> 3);
}
else if (fileChannel != null) {
flush();
fileChannel.position(this.position = position >> 3);
}
else {
if (wrapping) throw new UnsupportedOperationException("Illegal position: " + position);
throw new UnsupportedOperationException("position() can only be called if the underlying byte stream implements the RepositionableStream interface or if the getChannel() method of the underlying byte stream exists and returns a FileChannel");
}
final int residual = (int)(position & 7);
if (DEBUG) System.err.println(this + ": residual=" + residual);
if (residual != 0) {
current = read();
fill = 8 - residual;
}
}
/** Returns this stream bit position.
* @return this stream bit position. */
public long position() {
return ((this.position + pos) << 3) - fill;
}
/** Tests if this stream supports the {@link #mark(int)} and {@link #reset()} methods.
*
*
This method will just delegate the test to the underlying {@link InputStream}.
* @return whether this stream supports {@link #mark(int)}/{@link #reset()}.
*/
public boolean markSupported() {
return is.markSupported();
}
/** Marks the current position in this input stream. A subsequent call to
* the {@link #reset()} method repositions this stream at the last marked position so
* that subsequent reads re-read the same bits.
*
*
This method will just delegate the mark to the underlying {@link InputStream}.
* Moreover, it will throw an exception if you try to mark outsite byte boundaries.
*
* @param readLimit the maximum limit of bytes that can be read before the mark position becomes invalid.
* @throws IOException if you try to mark outside byte boundaries.
*/
public void mark(final int readLimit) throws IOException {
if (fill != 0) throw new IOException("You cannot mark a bit stream outside of byte boundaries.");
is.mark(readLimit);
}
/** Repositions this bit stream to the position at the time the {@link #mark(int)} method was last called.
*
*
This method will just {@link #flush() flush the stream} and delegate
* the reset to the underlying {@link InputStream}.
*/
public void reset() throws IOException {
flush();
is.reset();
}
/** Reads a natural number in unary coding.
*
* @return the next unary-encoded natural number.
* @see OutputBitStream#writeUnary(int)
*/
public int readUnary() throws IOException {
assert fill < 32 : fill + " >= " + 32;
int x;
if (fill < 16) refill();
x = Integer.numberOfLeadingZeros(current << (32 - fill));
if (x < fill) { // This works also when fill = 0
readBits += x + 1;
fill -= x + 1;
return x;
}
x = fill;
while((current = read()) == 0) x += 8;
x += 7 - (fill = 31 - Integer.numberOfLeadingZeros(current));
readBits += x + 1;
return x;
}
/** Reads a long natural number in unary coding.
*
* Note that by unary coding we mean that 1 encodes 0, 01 encodes 1 and so on.
*
* @return the next unary-encoded long natural number.
* @see OutputBitStream#writeUnary(int)
*/
public long readLongUnary() throws IOException {
assert fill < 32 : fill + " >= " + 32;
if (fill < 16) refill();
long x = Integer.numberOfLeadingZeros(current << (32 - fill));
if (x < fill) { // This works also when fill = 0
readBits += x + 1;
fill -= x + 1;
return x;
}
x = fill;
while((current = read()) == 0) x += 8;
x += 7 - (fill = 31 - Integer.numberOfLeadingZeros(current));
readBits += x + 1;
return x;
}
/** Reads a natural number in γ coding.
*
* @return the next γ-encoded natural number.
* @see OutputBitStream#writeGamma(int)
* @see #skipGammas(int)
*/
public int readGamma() throws IOException {
int preComp;
if ((fill >= 16 || refill() >= 16) && (preComp = GAMMA[current >> (fill - 16) & 0xFFFF]) != 0) {
readBits += preComp >> 16;
fill -= preComp >> 16;
return preComp & 0xFFFF;
}
final int msb = readUnary();
return ((1 << msb) | readInt(msb)) - 1;
}
/** Reads a long natural number in γ coding.
*
* @return the next γ-encoded long natural number.
* @see OutputBitStream#writeGamma(int)
* @see #skipGammas(int)
*/
public long readLongGamma() throws IOException {
int preComp;
if ((fill >= 16 || refill() >= 16) && (preComp = GAMMA[current >> (fill - 16) & 0xFFFF]) != 0) {
readBits += preComp >> 16;
fill -= preComp >> 16;
return preComp & 0xFFFF;
}
final int msb = readUnary();
return ((1L << msb) | readLong(msb)) - 1;
}
/** Skips a given amount of γ-coded natural numbers.
*
*
This method should be significantly quicker than iterating n
times on
* {@link #readGamma()} or {@link #readLongGamma()}, as precomputed tables are used directly,
* so the number of method calls is greatly reduced, and the result is discarded, so
* {@link #skip(long)} can be invoked instead of more specific decoding methods.
*
* @param n the number of γ-coded natural numbers to be skipped.
* @see #readGamma()
*/
public void skipGammas(long n) throws IOException {
int preComp;
while(n-- != 0) {
if ((fill >= 16 || refill() >= 16) && (preComp = GAMMA[current >> (fill - 16) & 0xFFFF] >> 16) != 0) {
readBits += preComp;
fill -= preComp;
continue;
}
skip((long)readUnary());
}
}
/** Skips a given amount of γ-coded natural numbers.
*
*
This method should be significantly quicker than iterating n
times on
* {@link #readGamma()} or {@link #readLongGamma()}, as precomputed tables are used directly,
* so the number of method calls is greatly reduced, and the result is discarded, so
* {@link #skip(long)} can be invoked instead of more specific decoding methods.
*
* @param n the number of γ-coded natural numbers to be skipped.
* @see #readGamma()
*/
public void skipGammas(int n) throws IOException {
skipGammas((long)n);
}
/** Reads a given amount of γ-coded natural numbers.
*
*
This method should be significantly quicker than iterating n
times on
* {@link #readGamma()}, as precomputed tables are used directly,
* so the number of method calls is greatly reduced.
*
* @param a an array of at least count
integers where the result
* will be written starting at the first position.
* @param count the number of γ-coded natural numbers to be read.
* @see #readGamma()
*/
public void readGammas(final int[] a, final int count) throws IOException {
int preComp, msb;
for(int i = 0; i < count; i++) {
if ((fill >= 16 || refill() >= 16) && (preComp = GAMMA[current >> (fill - 16) & 0xFFFF]) != 0) {
readBits += preComp >> 16;
fill -= preComp >> 16;
a[i] = preComp & 0xFFFF;
continue;
}
a[i] = ((1 << (msb = readUnary())) | readInt(msb)) - 1;
}
}
/** Reads a natural number in shifted γ coding.
*
* @return the next shifted-γ–encoded natural number.
* @see OutputBitStream#writeShiftedGamma(int)
* @see #skipShiftedGammas(int)
*/
public int readShiftedGamma() throws IOException {
int preComp;
if ((fill >= 16 || refill() >= 16) && (preComp = SHIFTED_GAMMA[current >> (fill - 16) & 0xFFFF]) != 0) {
readBits += preComp >> 16;
fill -= preComp >> 16;
return preComp & 0xFFFF;
}
final int msb = readUnary() - 1;
return msb == -1 ? 0 : ((1 << msb) | readInt(msb));
}
/** Reads a natural number in shifted γ coding.
*
* @return the next shifted-γ–encoded natural number.
* @see OutputBitStream#writeShiftedGamma(int)
* @see #skipShiftedGammas(int)
*/
public long readLongShiftedGamma() throws IOException {
int preComp;
if ((fill >= 16 || refill() >= 16) && (preComp = SHIFTED_GAMMA[current >> (fill - 16) & 0xFFFF]) != 0) {
readBits += preComp >> 16;
fill -= preComp >> 16;
return preComp & 0xFFFF;
}
final int msb = readUnary() - 1;
return msb == -1 ? 0 : ((1L << msb) | readLong(msb));
}
/** Skips a given amount of shifted-γ-coded natural numbers.
*
*
This method should be significantly quicker than iterating n
times on
* {@link #readShiftedGamma()} or {@link #readLongShiftedGamma()}, as precomputed tables are used directly,
* so the number of method calls is greatly reduced, and the result is discarded, so
* {@link #skip(long)} can be invoked instead of more specific decoding methods.
*
* @param n the number of shifted-γ-coded natural numbers to be skipped.
* @see #readShiftedGamma()
*/
public void skipShiftedGammas(long n) throws IOException {
int preComp;
while(n-- != 0) {
if ((fill >= 16 || refill() >= 16) && (preComp = SHIFTED_GAMMA[current >> (fill - 16) & 0xFFFF] >> 16) != 0) {
readBits += preComp;
fill -= preComp;
continue;
}
final long msb = readUnary() - 1;
if (msb > 0) skip(msb);
}
}
/** Skips a given amount of shifted-γ-coded natural numbers.
*
*
This method should be significantly quicker than iterating n
times on
* {@link #readShiftedGamma()} or {@link #readLongShiftedGamma()}, as precomputed tables are used directly,
* so the number of method calls is greatly reduced, and the result is discarded, so
* {@link #skip(long)} can be invoked instead of more specific decoding methods.
*
* @param n the number of shifted-γ-coded natural numbers to be skipped.
* @see #readShiftedGamma()
*/
public void skipShiftedGammas(int n) throws IOException {
skipShiftedGammas((long)n);
}
/** Reads a given amount of shifted-γ-coded natural numbers.
*
*
This method should be significantly quicker than iterating n
times on
* {@link #readShiftedGamma()}, as precomputed tables are used directly,
* so the number of method calls is greatly reduced.
*
* @param a an array of at least count
integers where the result
* will be written starting at the first position.
* @param count the number of shifted-γ-coded natural numbers to be read.
* @see #readShiftedGamma()
*/
public void readShiftedGammas(final int[] a, final int count) throws IOException {
int preComp, msb;
for(int i = 0; i < count; i++) {
if ((fill >= 16 || refill() >= 16) && (preComp = SHIFTED_GAMMA[current >> (fill - 16) & 0xFFFF]) != 0) {
readBits += preComp >> 16;
fill -= preComp >> 16;
a[i] = preComp & 0xFFFF;
continue;
}
msb = readUnary() - 1;
a[i] = msb == -1 ? 0 : ((1 << msb) | readInt(msb));
}
}
/** Reads a natural number in δ coding.
*
* @return the next δ-encoded natural number.
* @see OutputBitStream#writeDelta(int)
* @see #skipDeltas(int)
*/
public int readDelta() throws IOException {
int preComp;
if ((fill >= 16 || refill() >= 16) && (preComp = DELTA[current >> (fill - 16) & 0xFFFF]) != 0) {
readBits += preComp >> 16;
fill -= preComp >> 16;
return preComp & 0xFFFF;
}
final int msb = readGamma();
return ((1 << msb) | readInt(msb)) - 1;
}
/** Reads a long natural number in δ coding.
*
* @return the next δ-encoded long natural number.
* @see OutputBitStream#writeDelta(int)
* @see #skipDeltas(int)
*/
public long readLongDelta() throws IOException {
int preComp;
if ((fill >= 16 || refill() >= 16) && (preComp = DELTA[current >> (fill - 16) & 0xFFFF]) != 0) {
readBits += preComp >> 16;
fill -= preComp >> 16;
return preComp & 0xFFFF;
}
final int msb = readGamma();
return ((1L << msb) | readLong(msb)) - 1;
}
/** Skips a given amount of δ-coded natural numbers.
*
*
This method should be significantly quicker than iterating n
times on
* {@link #readDelta()} or {@link #readLongDelta()}, as precomputed tables are used directly,
* so the number of method calls is greatly reduced, and the result is discarded, so
* {@link #skip(long)} can be invoked instead of more specific decoding methods.
*
* @param n the number of δ-coded natural numbers to be skipped.
* @see #readDelta()
*/
public void skipDeltas(long n) throws IOException {
int preComp;
while(n-- != 0) {
if ((fill >= 16 || refill() >= 16) && (preComp = DELTA[current >> (fill - 16) & 0xFFFF] >> 16) != 0) {
readBits += preComp;
fill -= preComp;
continue;
}
skip((long)readGamma());
}
}
/** Skips a given amount of δ-coded natural numbers.
*
*
This method should be significantly quicker than iterating n
times on
* {@link #readDelta()} or {@link #readLongDelta()}, as precomputed tables are used directly,
* so the number of method calls is greatly reduced, and the result is discarded, so
* {@link #skip(long)} can be invoked instead of more specific decoding methods.
*
* @param n the number of δ-coded natural numbers to be skipped.
* @see #readDelta()
*/
public void skipDeltas(int n) throws IOException {
skipDeltas((long)n);
}
/** Reads a given amount of δ-coded natural numbers.
*
*
This method should be significantly quicker than iterating n
times on
* {@link #readDelta()}, as precomputed tables are used directly,
* so the number of method calls is greatly reduced.
*
* @param a an array of at least count
integers where the result
* will be written starting at the first position.
* @param count the number of δ-coded natural numbers to be read.
* @see #readDelta()
*/
public void readDeltas(final int[] a, final int count) throws IOException {
int preComp, msb;
for(int i = 0; i < count; i++) {
if ((fill >= 16 || refill() >= 16) && (preComp = DELTA[current >> (fill - 16) & 0xFFFF]) != 0) {
readBits += preComp >> 16;
fill -= preComp >> 16;
a[i] = preComp & 0xFFFF;
continue;
}
a[i] = ((1 << (msb = readGamma())) | readInt(msb)) - 1;
}
}
/** Reads a natural number in a limited range using a minimal binary coding.
*
* @param b a strict upper bound.
* @return the next minimally binary encoded natural number.
* @throws IllegalArgumentException if you try to read a negative number or use a nonpositive base.
* @see OutputBitStream#writeMinimalBinary(int, int)
*/
public int readMinimalBinary(final int b) throws IOException {
return readMinimalBinary(b, Fast.mostSignificantBit(b));
}
/** Reads a natural number in a limited range using a minimal binary coding.
*
* This method is faster than {@link #readMinimalBinary(int)} because it does not
* have to compute log2b
.
*
* @param b a strict upper bound.
* @param log2b the floor of the base-2 logarithm of the bound.
* @return the next minimally binary encoded natural number.
* @throws IllegalArgumentException if you try to read a negative number or use a nonpositive base.
* @see OutputBitStream#writeMinimalBinary(int, int)
*/
public int readMinimalBinary(final int b, final int log2b) throws IOException {
if (b < 1) throw new IllegalArgumentException("The bound " + b + " is not positive");
final int m = (1 << log2b + 1) - b;
final int x = readInt(log2b);
if (x < m) return x;
else return ((x << 1) + readBit() - m);
}
/** Reads a long natural number in a limited range using a minimal binary coding.
*
* @param b a strict upper bound.
* @return the next minimally binary encoded long natural number.
* @throws IllegalArgumentException if you try to read a negative number or use a nonpositive base.
* @see OutputBitStream#writeMinimalBinary(int, int)
*/
public long readLongMinimalBinary(final long b) throws IOException {
return readLongMinimalBinary(b, Fast.mostSignificantBit(b));
}
/** Reads a long natural number in a limited range using a minimal binary coding.
*
* This method is faster than {@link #readLongMinimalBinary(long)} because it does not
* have to compute log2b
.
*
* @param b a strict upper bound.
* @param log2b the floor of the base-2 logarithm of the bound.
* @return the next minimally binary encoded long natural number.
* @throws IllegalArgumentException if you try to read a negative number or use a nonpositive base.
* @see OutputBitStream#writeMinimalBinary(int, int)
*/
public long readLongMinimalBinary(final long b, final int log2b) throws IOException {
if (b < 1) throw new IllegalArgumentException("The bound " + b + " is not positive");
final long m = (1L << log2b + 1) - b;
final long x = readLong(log2b);
if (x < m) return x;
else return ((x << 1) + readBit() - m);
}
/** Reads a natural number in Golomb coding.
*
*
This method implements also the case in which b
is 0: in this case,
* nothing will be read, and 0 will be returned.
*
* @param b the modulus for the coding.
* @return the next Golomb-encoded natural number.
* @throws IllegalArgumentException if you use a nonpositive modulus.
* @see OutputBitStream#writeGolomb(int, int)
*/
public int readGolomb(final int b) throws IOException {
return readGolomb(b, Fast.mostSignificantBit(b));
}
/** Reads a natural number in Golomb coding.
*
* This method is faster than {@link #readGolomb(int)} because it does not
* have to compute log2b
.
*
*
This method implements also the case in which b
is 0: in this case,
* nothing will be read, and 0 will be returned.
*
* @param b the modulus for the coding.
* @param log2b the floor of the base-2 logarithm of the coding modulus.
* @return the next Golomb-encoded natural number.
* @throws IllegalArgumentException if you use a nonpositive modulus.
* @see OutputBitStream#writeGolomb(int, int)
*/
public int readGolomb(final int b, final int log2b) throws IOException {
if (b < 0) throw new IllegalArgumentException("The modulus " + b + " is negative");
if (b == 0) return 0;
return readUnary() * b + readMinimalBinary(b, log2b);
}
/** Reads a long natural number in Golomb coding.
*
*
This method implements also the case in which b
is 0: in this case,
* nothing will be read, and 0 will be returned.
*
* @param b the modulus for the coding.
* @return the next Golomb-encoded long natural number.
* @throws IllegalArgumentException if you use a nonpositive modulus.
* @see OutputBitStream#writeGolomb(int, int)
*/
public long readLongGolomb(final long b) throws IOException {
return readLongGolomb(b, Fast.mostSignificantBit(b));
}
/** Reads a long natural number in Golomb coding.
*
* This method is faster than {@link #readLongGolomb(long)} because it does not
* have to compute log2b
.
*
*
This method implements also the case in which b
is 0: in this case,
* nothing will be read, and 0 will be returned.
*
* @param b the modulus for the coding.
* @param log2b the floor of the base-2 logarithm of the coding modulus.
* @return the next Golomb-encoded long natural number.
* @throws IllegalArgumentException if you use a nonpositive modulus.
* @see OutputBitStream#writeGolomb(int, int)
*/
public long readLongGolomb(final long b, final int log2b) throws IOException {
if (b < 0) throw new IllegalArgumentException("The modulus " + b + " is negative");
if (b == 0) return 0;
return readUnary() * b + readLongMinimalBinary(b, log2b);
}
/** Reads a natural number in skewed Golomb coding.
*
*
This method implements also the case in which b
is 0: in this case,
* nothing will be read, and 0 will be returned.
*
* @param b the modulus for the coding.
* @return the next skewed Golomb-encoded natural number.
* @throws IllegalArgumentException if you use a negative modulus.
* @see OutputBitStream#writeSkewedGolomb(int, int)
*/
public int readSkewedGolomb(final int b) throws IOException {
if (b < 0) throw new IllegalArgumentException("The modulus " + b + " is negative");
if (b == 0) return 0;
final int M = ((1 << readUnary() + 1) - 1) * b;
final int m = (M / (2 * b)) * b;
return m + readMinimalBinary(M - m);
}
/** Reads a long natural number in skewed Golomb coding.
*
*
This method implements also the case in which b
is 0: in this case,
* nothing will be read, and 0 will be returned.
*
* @param b the modulus for the coding.
* @return the next skewed Golomb-encoded long natural number.
* @throws IllegalArgumentException if you use a negative modulus.
* @see OutputBitStream#writeSkewedGolomb(int, int)
*/
public long readLongSkewedGolomb(final long b) throws IOException {
if (b < 0) throw new IllegalArgumentException("The modulus " + b + " is negative");
if (b == 0) return 0;
final long M = ((1L << readUnary() + 1) - 1) * b;
final long m = (M / (2 * b)) * b;
return m + readLongMinimalBinary(M - m);
}
/** Reads a natural number in ζ coding.
*
* @param k the shrinking factor.
* @return the next ζ-encoded natural number.
* @throws IllegalArgumentException if you use a nonpositive shrinking factor.
* @see OutputBitStream#writeZeta(int, int)
*/
public int readZeta(final int k) throws IOException {
if (k < 1) throw new IllegalArgumentException("The shrinking factor " + k + " is not positive");
if (k == 3) {
int preComp;
if ((fill >= 16 || refill() >= 16) && (preComp = ZETA_3[current >> (fill - 16) & 0xFFFF]) != 0) {
readBits += preComp >> 16;
fill -= preComp >> 16;
return preComp & 0xFFFF;
}
}
final int h = readUnary();
final int left = 1 << h * k;
final int m = readInt(h * k + k - 1);
if (m < left) return m + left - 1;
return (m << 1) + readBit() - 1;
}
/** Reads a long natural number in ζ coding.
*
* @param k the shrinking factor.
* @return the next ζ-encoded long natural number.
* @throws IllegalArgumentException if you use a nonpositive shrinking factor.
* @see OutputBitStream#writeZeta(int, int)
*/
public long readLongZeta(final int k) throws IOException {
if (k < 1) throw new IllegalArgumentException("The shrinking factor " + k + " is not positive");
if (k == 3) {
int preComp;
if ((fill >= 16 || refill() >= 16) && (preComp = ZETA_3[current >> (fill - 16) & 0xFFFF]) != 0) {
readBits += preComp >> 16;
fill -= preComp >> 16;
return preComp & 0xFFFF;
}
}
final int h = readUnary();
final long left = 1L << h * k;
final long m = readLong(h * k + k - 1);
if (m < left) return m + left - 1;
return (m << 1) + readBit() - 1;
}
/** Skips a given amount of ζ-coded natural numbers.
*
*
This method should be significantly quicker than iterating n
times on
* {@link #readZeta(int)} or {@link #readLongZeta(int)}, as precomputed tables are used directly,
* so the number of method calls is greatly reduced, and the result is discarded, so
* {@link #skip(long)} can be invoked instead of more specific decoding methods.
*
*
* @param k the shrinking factor.
* @param n the number of ζ-coded natural numbers to be skipped.
* @see #readZeta(int)
*/
public void skipZetas(final int k, long n) throws IOException {
int h, preComp;
while(n-- != 0) {
if (k == 3 && (fill >= 16 || refill() >= 16) && (preComp = ZETA_3[current >> (fill - 16) & 0xFFFF] >> 16) != 0) {
readBits += preComp;
fill -= preComp;
continue;
}
h = readUnary();
if (readInt(h * k + k - 1) >= 1 << h * k) skip(1L);
}
}
/** Skips a given amount of ζ-coded natural numbers.
*
*
This method should be significantly quicker than iterating n
times on
* {@link #readZeta(int)} or {@link #readLongZeta(int)}, as precomputed tables are used directly,
* so the number of method calls is greatly reduced, and the result is discarded, so
* {@link #skip(long)} can be invoked instead of more specific decoding methods.
*
*
* @param k the shrinking factor.
* @param n the number of ζ-coded natural numbers to be skipped.
* @see #readZeta(int)
*/
public void skipZetas(final int k, int n) throws IOException {
skipZetas(k, (long)n);
}
/** Reads a given amount of γ-coded natural numbers.
*
*
This method should be significantly quicker than iterating n
times on
* {@link #readGamma()}, as precomputed tables are used directly,
* so the number of method calls is greatly reduced.
*
* @param k the shrinking factor.
* @param a an array of at least count
integers where the result
* will be written starting at the first position.
* @param count the number of ζ-coded natural numbers to be read.
* @see #readGamma()
*/
public void readZetas(final int k, final int[] a, final int count) throws IOException {
int h, left, m;
int preComp;
for(int i = 0; i < count; i++) {
if (k == 3 && (fill >= 16 || refill() >= 16) && (preComp = ZETA_3[current >> (fill - 16) & 0xFFFF]) != 0) {
readBits += preComp >> 16;
fill -= preComp >> 16;
a[i] = preComp & 0xFFFF;
continue;
}
h = readUnary();
left = 1 << h * k;
m = readInt(h * k + k - 1);
a[i] = m < left ? m + left - 1 : (m << 1) + readBit() - 1;
}
}
/** Reads a natural number in variable-length nibble coding.
*
* @return the next variable-length nibble-encoded natural number.
* @see OutputBitStream#writeNibble(int)
*/
public int readNibble() throws IOException {
int b;
int x = 0;
do {
x <<= 3;
b = readBit();
x |= readInt(3);
} while(b == 0);
return x;
}
/** Reads a long natural number in variable-length nibble coding.
*
* @return the next variable-length nibble-encoded long natural number.
* @see OutputBitStream#writeNibble(int)
*/
public long readLongNibble() throws IOException {
int b;
long x = 0;
do {
x <<= 3;
b = readBit();
x |= readInt(3);
} while(b == 0);
return x;
}
@Override
public boolean hasNext() {
return true;
}
@Override
public boolean nextBoolean() {
try {
return readBit() != 0;
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
/** Skips over the given number of bits.
*
* @param n the number of bits to skip.
* @return the number of bits actually skipped.
* @deprecated This method is simply an expensive, try/catch-surrounded version
* of {@link #skip(long)} that is made necessary by the interface
* by {@link BooleanIterator}.
*/
@Override
@Deprecated
public int skip(final int n) {
try {
return (int)skip((long)n);
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
/** Copies a given number of bits from this input bit stream into a given output bit stream.
*
* @param obs an output bit stream.
* @param length the number of bits to copy.
* @throws EOFException if there are not enough bits to copy.
*/
public void copyTo(final OutputBitStream obs, long length) throws IOException {
final byte[] buffer = new byte[64 * 1024];
while(length > 0) {
final int toRead = (int)Math.min(length, buffer.length * Byte.SIZE);
read(buffer, toRead);
obs.write(buffer, 0, toRead);
length -= toRead;
}
}
}