All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.it.unimi.dsi.io.OutputBitStream Maven / Gradle / Ivy

Go to download

The DSI utilities are a mishmash of classes accumulated during the last twenty years in projects developed at the DSI (Dipartimento di Scienze dell'Informazione, i.e., Information Sciences Department), now DI (Dipartimento di Informatica, i.e., Informatics Department), of the Universita` degli Studi di Milano.

There is a newer version: 2.7.3
Show newest version
package it.unimi.dsi.io;

/*
 * DSI utilities
 *
 * Copyright (C) 2002-2019 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.io.RepositionableStream;

import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.Flushable;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.nio.channels.FileChannel;


/** Bit-level output stream.
 *
 * 

This class wraps any {@link OutputStream} so that you can treat it as * bit stream. Constructors and methods closely resemble those of * {@link OutputStream}. Data can be added to such a stream in several ways: * writing an integer or long in fixed-width, unary, γ, δ, ζ and Golomb * coding, or providing a vector of bytes. * *

This class can also {@linkplain #OutputBitStream(byte[]) wrap a byte * array}; this is much more lightweight than wrapping a {@link * it.unimi.dsi.fastutil.io.FastByteArrayOutputStream} wrapping the array, but overflowing the array * will cause an {@link java.io.IOException}. * *

Note that when writing using a vector of bytes bits are written in the natural * way: 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 * writing integers using some coding, instead, the lower bits are considered * for coding (in the fixed-width case, the given number of bits, otherwise * the lower bits starting from the most significant one). * *

The bit stream format

* *

The bit streams written by this class are big endian. That is, * the first bit of the stream 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. * *

Blocks of bits (such as coded integers) are written starting from the * most significant bit. In other words, if you take the first bytes of a stream * and print them in binary you will see exactly the sequence of bits you have * written. In particular, if you write 32-bit integers you will get a stream * which is identical to the one produced by a {@link java.io.DataOutput}. * *

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 byte-aligns the streams, flushes to the underlying byte stream * all data and resets the internal state. At this point, you can safely reposition * the underlying stream and write again afterwards. For instance, this is safe * and will perform as expected: *
     * FileOutputStream fos = new FileOutputStream(...);
     * OutputBitStream obs = new OutputBitStream(fos);
     * ... write operations on obs ...
     * obs.flush();
     * fos.getChannel().position(...);
     * ... other write operations on obs ...
     * 
    * *

    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 output 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). The * specified position must be byte aligned, as there is no clean way of reading * a fraction of a byte with the current APIs. However, since the reflective checks are quite * heavy they can be disabled using a {@linkplain OutputBitStream#OutputBitStream(OutputStream, boolean) suitable constructor}. * *

* *

This class is not synchronised. If multiple threads * access an instance of this class concurrently, they must be synchronised externally. * * @see java.io.OutputStream * @see it.unimi.dsi.io.InputBitStream * @author Sebastiano Vigna * @since 0.1 */ public class OutputBitStream implements Flushable, Closeable { public static final int MAX_PRECOMPUTED = 4096; private final static boolean DEBUG = false; /* Precomputed tables: the lower 24 bits contain the (right-aligned) code, * the upper 8 bits contain the code length. */ public static final int[] GAMMA = new int[MAX_PRECOMPUTED], DELTA = new int[MAX_PRECOMPUTED], ZETA_3 = new int[MAX_PRECOMPUTED], SHIFTED_GAMMA = new int[MAX_PRECOMPUTED]; static { /* We load all precomputed arrays from resource files, * to work around the limit on static initialiser code. */ try { InputBitStream.fillArrayFromResource("gamma.out.12", GAMMA); InputBitStream.fillArrayFromResource("delta.out.12", DELTA); InputBitStream.fillArrayFromResource("zeta3.out.12", ZETA_3); InputBitStream.fillArrayFromResource("shiftedgamma.out.12", SHIFTED_GAMMA); } catch (IOException e) { throw new RuntimeException(e); } } /** The default size of the byte buffer in bytes (16Ki). */ public final static int DEFAULT_BUFFER_SIZE = 16 * 1024; /** The underlying {@link OutputStream}. */ protected final OutputStream os; /** The number of bits written to this bit stream. */ private long writtenBits; /** Current bit buffer. */ private int current; /** The stream buffer. */ protected byte[] buffer; /** Current number of free bits in the bit buffer (the bits in the buffer are stored high). */ protected int free; /** Current position in the byte buffer. */ protected int pos; /** Current position of the underlying output stream. */ protected long position; /** Current number of bytes available in the byte buffer. */ protected int avail; /** Size of the small buffer for temporary usage. */ final static int TEMP_BUFFER_SIZE = 128; /** The cached file channel underlying {@link #os}. */ protected final FileChannel fileChannel; /** {@link #os} cast to a positionable stream. */ protected final RepositionableStream repositionableStream; /** True if we are wrapping an array. */ protected final boolean wrapping; /** This (non-public) constructor exists just to provide fake initialisation for classes such as {@link DebugOutputBitStream}. */ protected OutputBitStream() { os = null; fileChannel = null; repositionableStream = null; wrapping = false; } /** Creates a new output bit stream wrapping a given output 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 os the output stream to wrap. */ public OutputBitStream(final OutputStream os) { this(os, true); } /** Creates a new output bit stream wrapping a given output stream using a buffer of size {@link #DEFAULT_BUFFER_SIZE}. * * @param os the output stream to wrap. * @param testForPosition if false, the reflective test that is necessary to support {@link #position(long)} * in case os does not support {@link RepositionableStream} will not be performed. */ public OutputBitStream(final OutputStream os, final boolean testForPosition) { this(os, DEFAULT_BUFFER_SIZE); } /** Creates a new output bit stream wrapping a given output stream with a specified buffer size. * *

This constructor performs the reflective tests that are necessary to support {@link #position(long)}. * * @param os the output stream to wrap. * @param bufSize the size in byte of the buffer; it may be 0, denoting no buffering. */ public OutputBitStream(final OutputStream os, final int bufSize) { this(os, bufSize, true); } /** Creates a new output bit stream wrapping a given output stream with a specified buffer size. * * @param os the output 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 os does not support {@link RepositionableStream} will not be performed. */ public OutputBitStream(final OutputStream os, final int bufSize, final boolean testForPosition) { this.os = os; wrapping = false; if (bufSize != 0) { this.buffer = new byte[bufSize]; avail = bufSize; } free = 8; if (os instanceof RepositionableStream) { repositionableStream = (RepositionableStream)os; fileChannel = null; } else if (testForPosition) { FileChannel fc = null; try { fc = (FileChannel)(os.getClass().getMethod("getChannel")).invoke(os, new Object[] {}); } 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 output bit stream wrapping a given file output stream using a buffer of size {@link #DEFAULT_BUFFER_SIZE}. * *

This constructor invokes directly {@link FileOutputStream#getChannel()} to support {@link #position(long)}. * * @param os the output stream to wrap. */ public OutputBitStream(final FileOutputStream os) { this(os, DEFAULT_BUFFER_SIZE); } /** Creates a new output bit stream wrapping a given file output stream with a specified buffer size. * *

This constructor invokes directly {@link FileOutputStream#getChannel()} to support {@link #position(long)}. * * @param os the output stream to wrap. * @param bufSize the size in byte of the buffer; it may be 0, denoting no buffering. */ public OutputBitStream(final FileOutputStream os, final int bufSize) { this.os = os; wrapping = false; if (bufSize != 0) { this.buffer = new byte[bufSize]; avail = bufSize; } free = 8; repositionableStream = null; fileChannel = os.getChannel(); } /** Creates a new output bit stream wrapping a given byte array. * * @param a the byte array to wrap. */ public OutputBitStream(final byte[] a) { os = null; free = 8; buffer = a; avail = a.length; wrapping = true; fileChannel = null; repositionableStream = null; } /** Creates a new output bit stream writing to file. * * @param name the name of the file. * @param bufSize the size in byte of the buffer; it may be 0, denoting no buffering. */ public OutputBitStream(final String name, final int bufSize) throws FileNotFoundException { this(new FileOutputStream(name), bufSize); } /** Creates a new output bit stream writing to a file. * * @param name the name of the file. */ public OutputBitStream(final String name) throws FileNotFoundException { this(new FileOutputStream(name), DEFAULT_BUFFER_SIZE); } /** Creates a new output bit stream writing to file. * * @param file the file. * @param bufSize the size in byte of the buffer; it may be 0, denoting no buffering. */ public OutputBitStream(final File file, final int bufSize) throws FileNotFoundException { this(new FileOutputStream(file), bufSize); } /** Creates a new output bit stream writing to a file. * * @param file the file. */ public OutputBitStream(final File file) throws FileNotFoundException { this(new FileOutputStream(file), DEFAULT_BUFFER_SIZE); } /** Flushes the bit stream. * *

This method will align the stream, write the bit buffer, empty the * byte buffer and delegate to the {@link OutputStream#flush()} method of * the underlying output stream. * *

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 * write to the underlying output stream will start with the content of the * first write method called afterwards. */ @Override public void flush() throws IOException { align(); if (os != null) { if (buffer != null) { os.write(buffer, 0, pos); position += pos; pos = 0; avail = buffer.length; } os.flush(); } } /** Closes the bit stream. All resources associated with the stream are released. */ @Override public void close() throws IOException { flush(); if (os != null && os != System.out && os != System.err) os.close(); buffer = null; } /** Returns the number of bits written to this bit stream. * * @return the number of bits written so far. */ public long writtenBits() { return writtenBits; } /** Sets the number of bits written to this bit stream. * *

This method is provided so that, for instance, the * user can reset via writtenBits(0) the written-bits count * after a {@link #flush()}. * * @param writtenBits the new value for the number of bits written so far. */ public void writtenBits(final long writtenBits) { this.writtenBits = writtenBits; } /** Writes a byte to the stream. * *

This method takes care of managing the buffering logic transparently. * *

However, this method does not update {@link #writtenBits}. * The caller should increment {@link #writtenBits} by 8 at each call. */ private void write(final int b) throws IOException { if (avail-- == 0) { if (os == null) { avail = 0; throw new IOException("Array full"); } if (buffer == null) { os.write(b); position++; avail = 0; return; } os.write(buffer); position += buffer.length; avail = buffer.length - 1; pos = 0; } buffer[pos++] = (byte)b; } /** Writes bits in the bit buffer, possibly flushing it. * * You cannot write more than {@link #free} bits with this method. However, * after having written {@link #free} bits the bit buffer will be empty. In * particular, there should never be 0 free bits in the buffer. * * @param b the bits to write in the lower positions; the remaining positions must be zero. * @param len the number of bits to write (0 is safe and causes no action). * @return the number of bits written. * @throws IllegalArgumentException if one tries to write more bits than available in the buffer and debug is enabled. */ private int writeInCurrent(final int b, final int len) throws IOException { //System.err.println("Writing " + len + " bits out of " + Fast.binary(b)); if (DEBUG) if (len > free) throw new IllegalArgumentException(Integer.toString(len) + " bit(s) to write, " + free + " available."); current |= (b & ((1 << len) - 1)) << (free -= len); if (free == 0) { write(current); free = 8; current = 0; } writtenBits += len; return len; } /** Aligns the stream. * * After a call to this method, the stream is byte aligned. Zeroes * are used to pad it if necessary. * * @return the number of padding bits. */ public int align() throws IOException { if (free != 8) return writeInCurrent(0, free); else return 0; } /** 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. Currently there is no clean, working way of supporting * out-of-byte-boundary positioning. * * @param position the new position expressed as a bit offset; it must be byte-aligned. * @throws IllegalArgumentException when trying to position outside of byte boundaries. * @throws UnsupportedOperationException if the underlying byte stream does not implement * {@link RepositionableStream} and 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 (position < 0) throw new IllegalArgumentException("Illegal position: " + position); if ((position & 7) != 0) throw new IllegalArgumentException("Not a byte-aligned position: " + position); if (wrapping) { if ((position >>> 3) > buffer.length) throw new IllegalArgumentException("Illegal position: " + position); flush(); free = 8; pos = (int)(position >>> 3); avail = buffer.length - pos; } else if (repositionableStream != null) { flush(); if (position >>> 3 != this.position) repositionableStream.position(this.position = position >>> 3); } else if (fileChannel != null) { flush(); if (position >>> 3 != this.position) fileChannel.position(this.position = position >>> 3); } else 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"); } /** Writes a sequence of bits. * * Bits will be written 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 a vector containing the bits to be written. * @param len a bit length. * @return the number of bits written (len). */ public long write(final byte[] bits, final long len) throws IOException { return writeByteOffset(bits, 0, len); } /** Writes a sequence of bits, starting from a given offset. * * Bits will be written 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 a vector containing the bits to be written. * @param offset a bit offset from which to start to write. * @param len a bit length. * @return the number of bits written (len). */ public long write(final byte[] bits, final long offset, final long len) throws IOException { final int initial = (int)(8 - (offset & 0x7)); if (initial == 8) return writeByteOffset(bits, (int)offset / 8, len); if (len <= initial) return writeInt((0xFF & bits[(int)(offset / 8)]) >>> (initial - len), (int)len); return writeInt(bits[(int)(offset / 8)], initial) + writeByteOffset(bits, (int)(offset / 8 + 1), len - initial); } /** Writes a sequence of bits, starting from a given byte offset. * * Bits will be written 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. * *

This method is used to support methods such as {@link #write(byte[], long, long)}. * * @param bits a vector containing the bits to be written. * @param offset an offset, expressed in bytes. * @param len a bit length. * @return the number of bits written (len). */ protected long writeByteOffset(final byte[] bits, final int offset, long len) throws IOException { if (len == 0) return 0; if (len <= free) { return writeInCurrent(bits[offset] >>> 8 - len, (int)len); } else { final int shift = free; int i, j; writeInCurrent(bits[offset] >>> 8 - shift, shift); len -= shift; j = offset; i = (int)(len >> 3); while(i-- != 0) { write(bits[j] << shift | (bits[j + 1] & 0xFF) >>> 8 - shift); writtenBits += 8; j++; } final int queue = (int)(len & 7); if (queue != 0) { if (queue <= 8 - shift) { writeInCurrent(bits[j] >>> 8 - shift - queue, queue); } else { writeInCurrent(bits[j], 8 - shift); writeInCurrent(bits[j + 1] >>> 16 - queue - shift, queue + shift - 8); } } return len + shift; } } /** Writes a bit. * * @param bit a bit. * @return the number of bits written. */ public int writeBit(final boolean bit) throws IOException { return writeInCurrent(bit ? 1 : 0, 1); } /** Writes a bit. * * @param bit a bit. * @return the number of bits written. */ public int writeBit(final int bit) throws IOException { if (bit < 0 || bit > 1) throw new IllegalArgumentException("The argument " + bit + " is not a bit."); return writeInCurrent(bit, 1); } /** Writes a sequence of bits emitted by a boolean iterator. * *

If the iterator throws an exception, it is catched, * and the return value is given by the number of bits written * increased by one and with the sign changed. * * @param i a boolean iterator. * @return if i did not throw a runtime exception, * the number of bits written; otherwise, the number of bits written, * plus one, with the sign changed. */ public int write(final BooleanIterator i) throws IOException { int count = 0; boolean bit; while(i.hasNext()) { try { bit = i.nextBoolean(); } catch(RuntimeException hide) { return -count - 1; } writeBit(bit); count++; } return count; } /** Writes a fixed number of bits from an integer. * * @param x an integer. * @param len a bit length; this many lower bits of the first argument will be written * (the most significant bit first). * @return the number of bits written (len). */ public int writeInt(int x, final int len) throws IOException { if (len < 0 || len > 32) throw new IllegalArgumentException("You cannot write " + len + " bits to an integer."); if (len <= free) return writeInCurrent(x, len); int i = len - free; final int queue = i & 7; if (free != 0) writeInCurrent(x >>> i, free); // Dirty trick: since queue < 8, we pre-write the last bits in the bit buffer. if (queue != 0) { i -= queue; writeInCurrent(x, queue); x >>>= queue; } if (i == 32) write(x >>> 24); if (i > 23) write(x >>> 16); if (i > 15) write(x >>> 8); if (i > 7) write(x); writtenBits += i; return len; } /** Writes a fixed number of bits from a long. * * @param x a long. * @param len a bit length; this many lower bits of the first argument will be written * (the most significant bit first). * @return the number of bits written (len). */ public int writeLong(long x, final int len) throws IOException { if (len < 0 || len > 64) throw new IllegalArgumentException("You cannot write " + len + " bits to a long."); if (len <= free) return writeInCurrent((int)x, len); int i = len - free; final int queue = i & 7; if (free != 0) writeInCurrent((int)(x >>> i), free); // Dirty trick: since queue < 8, we pre-write the last bits in the bit buffer. if (queue != 0) { i -= queue; writeInCurrent((int)x, queue); x >>>= queue; } if (i == 64) write((int)(x >>> 56)); if (i > 55) write((int)(x >>> 48)); if (i > 47) write((int)(x >>> 40)); if (i > 39) write((int)(x >>> 32)); if (i > 31) write((int)x >>> 24); if (i > 23) write((int)x >>> 16); if (i > 15) write((int)x >>> 8); if (i > 7) write((int)x); writtenBits += i; return len; } /** Writes a natural number in unary coding. * *

The unary coding of a natural number n is given * by 0n1. * * @param x a natural number. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number. */ public int writeUnary(int x) throws IOException { if (x < 0) throw new IllegalArgumentException("The argument " + x + " is negative"); if (x < free) return writeInCurrent(1, x + 1); final int shift = free; x -= shift; writtenBits += shift; write(current); free = 8; current = 0; int i = x >> 3; writtenBits += (x & 0x7FFFFFF8); while(i-- != 0) write(0); writeInCurrent(1, (x & 7) + 1); return x + shift + 1; } /** Writes a long natural number in unary coding. * * @param x a long natural number. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number. * @see #writeUnary(int) */ public long writeLongUnary(long x) throws IOException { if (x < 0) throw new IllegalArgumentException("The argument " + x + " is negative"); if (x < free) return writeInCurrent(1, (int)x + 1); final int shift = free; x -= shift; writtenBits += shift; write(current); free = 8; current = 0; long i = x >> 3; writtenBits += (x & 0x7FFFFFFFFFFFFFF8L); while(i-- != 0) write(0); writeInCurrent(1, (int)(x & 7) + 1); return x + shift + 1; } /** Writes a natural number in γ coding. * *

The γ coding of a positive number of k bits is * obtained writing k-1 in unary, followed by the lower * k-1 bits of the number. The coding of a natural number is * obtained by adding one and coding. * * @param x a natural number. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number. */ public int writeGamma(int x) throws IOException { if (x < 0) throw new IllegalArgumentException("The argument " + x + " is negative"); if (x < MAX_PRECOMPUTED) return writeInt(GAMMA[x], GAMMA[x] >>> 26); final int msb = Fast.mostSignificantBit(++x); return writeUnary(msb) + writeInt(x, msb); } /** Writes a given amount of natural numbers in γ coding. * * @param a an array at least count natural numbers. * @param count the number of elements of a to be written. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number. * @see #writeGamma(int) */ public long writeGammas(final int[] a, final int count) throws IOException { long l = 0; for(int i = 0; i < count; i++) { int x = a[i]; if (x < MAX_PRECOMPUTED) { l += writeInt(GAMMA[x], GAMMA[x] >>> 26); continue; } final int msb = Fast.mostSignificantBit(++x); l += writeUnary(msb) + writeInt(x, msb); } return l; } /** Writes a long natural number in γ coding. * * @param x a long natural number. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number. * @see #writeGamma(int) */ public int writeLongGamma(long x) throws IOException { if (x < 0) throw new IllegalArgumentException("The argument " + x + " is negative"); if (x < MAX_PRECOMPUTED) return writeInt(GAMMA[(int)x], GAMMA[(int)x] >>> 26); final int msb = Fast.mostSignificantBit(++x); return writeUnary(msb) + writeLong(x, msb); } /** Writes a natural number in shifted γ coding. * * The shifted γ coding of 0 is 1. The coding of a positive number * of k bits is * obtained writing k in unary, followed by the lower * k-1 bits of the number (equivalently, by writing * k zeroes followed by the number). * * @param x a natural number. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number. */ public int writeShiftedGamma(int x) throws IOException { if (x < 0) throw new IllegalArgumentException("The argument " + x + " is negative"); if (x < MAX_PRECOMPUTED) return writeInt(SHIFTED_GAMMA[x], SHIFTED_GAMMA[x] >>> 26); final int msb = Fast.mostSignificantBit(x); return writeUnary(msb + 1) + (msb > 0 ? writeInt(x, msb) : 0); } /** Writes a long natural number in shifted γ coding. * * @param x a natural number. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number. * @see #writeShiftedGamma(int) */ public int writeLongShiftedGamma(long x) throws IOException { if (x < 0) throw new IllegalArgumentException("The argument " + x + " is negative"); if (x < MAX_PRECOMPUTED) return writeInt(SHIFTED_GAMMA[(int)x], SHIFTED_GAMMA[(int)x] >>> 26); final int msb = Fast.mostSignificantBit(x); return writeUnary(msb + 1) + (msb > 0 ? writeLong(x, msb) : 0); } /** Writes a given amount of natural numbers in shifted γ coding. * * @param a an array at least count natural numbers. * @param count the number of elements of a to be written. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number. * @see #writeShiftedGamma(int) */ public long writeShiftedGammas(final int[] a, final int count) throws IOException { long l = 0; for(int i = 0; i < count; i++) { int x = a[i]; if (x < MAX_PRECOMPUTED) { l += writeInt(SHIFTED_GAMMA[x], SHIFTED_GAMMA[x] >>> 26); continue; } final int msb = Fast.mostSignificantBit(x); l += writeUnary(msb + 1) + (msb > 0 ? writeInt(x, msb) : 0); } return l; } /** Writes a natural number in δ coding. * * The δ coding of a positive number of k bits is * obtained writing k-1 in γ coding, followed by the * lower k-1 bits of the number. The coding of a natural * number is obtained by adding one and coding. * * @param x a natural number. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number. */ public int writeDelta(int x) throws IOException { if (x < 0) throw new IllegalArgumentException("The argument " + x + " is negative"); if (x < MAX_PRECOMPUTED) return writeInt(DELTA[x], DELTA[x] >>> 26); final int msb = Fast.mostSignificantBit(++x); return writeGamma(msb) + writeInt(x, msb); } /** Writes a long natural number in δ coding. * * @param x a long natural number. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number. * @see #writeDelta(int) */ public int writeLongDelta(long x) throws IOException { if (x < 0) throw new IllegalArgumentException("The argument " + x + " is negative"); if (x < MAX_PRECOMPUTED) return writeInt(DELTA[(int)x], DELTA[(int)x] >>> 26); final int msb = Fast.mostSignificantBit(++x); return writeGamma(msb) + writeLong(x, msb); } /** Writes a given amount of natural numbers in δ coding. * * @param a an array at least count natural numbers. * @param count the number of elements of a to be written. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number. * @see #writeDelta(int) */ public long writeDeltas(final int[] a, final int count) throws IOException { long l = 0; for(int i = 0; i < count; i++) { int x = a[i]; if (x < MAX_PRECOMPUTED) { l += writeInt(DELTA[x], DELTA[x] >>> 26); continue; } final int msb = Fast.mostSignificantBit(++x); l += writeGamma(msb) + writeInt(x, msb); } return l; } /** Writes a natural number in a limited range using a minimal binary coding. * *

A minimal binary code is an optimal code for the uniform distribution. * This method uses an optimal code in which shorter words are assigned to * smaller integers. * * @param x a natural number. * @param b a strict upper bound for x. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number or use a nonpositive base. */ public int writeMinimalBinary(final int x, final int b) throws IOException { if (b < 1) throw new IllegalArgumentException("The bound " + b + " is not positive"); return writeMinimalBinary(x, b, Fast.mostSignificantBit(b)); } /** Writes a natural number in a limited range using a minimal binary coding. * * This method is faster than {@link #writeMinimalBinary(int,int)} because it does not * have to compute log2b. * * @param x a natural number. * @param b a strict upper bound for x. * @param log2b the floor of the base-2 logarithm of the bound. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number or use a nonpositive base. * @see #writeMinimalBinary(int, int) */ public int writeMinimalBinary(final int x, final int b, final int log2b) throws IOException { if (x < 0) throw new IllegalArgumentException("The argument " + x + " is negative"); if (b < 1) throw new IllegalArgumentException("The bound " + b + " is not positive"); if (x >= b) throw new IllegalArgumentException("The argument " + x + " exceeds the bound " + b); // Numbers smaller than m are encoded in log2b bits. final int m = (1 << log2b + 1) - b; if (x < m) return writeInt(x, log2b); else return writeInt(m + x, log2b + 1); } /** Writes a long natural number in a limited range using a minimal binary coding. * * @param x a natural number. * @param b a strict upper bound for x. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number or use a nonpositive base. * @see #writeMinimalBinary(int, int) */ public int writeLongMinimalBinary(final long x, final long b) throws IOException { if (b < 1) throw new IllegalArgumentException("The bound " + b + " is not positive"); return writeLongMinimalBinary(x, b, Fast.mostSignificantBit(b)); } /** Writes a long natural number in a limited range using a minimal binary coding. * * This method is faster than {@link #writeLongMinimalBinary(long,long)} because it does not * have to compute log2b. * * @param x a long natural number. * @param b a strict upper bound for x. * @param log2b the floor of the base-2 logarithm of the bound. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number or use a nonpositive base. * @see #writeMinimalBinary(int, int) */ public int writeLongMinimalBinary(final long x, final long b, final int log2b) throws IOException { if (x < 0) throw new IllegalArgumentException("The argument " + x + " is negative"); if (b < 1) throw new IllegalArgumentException("The bound " + b + " is not positive"); if (x >= b) throw new IllegalArgumentException("The argument " + x + " exceeds the bound " + b); // Numbers smaller than m are encoded in log2b bits. final long m = (1L << log2b + 1) - b; if (x < m) return writeLong(x, log2b); else return writeLong(m + x, log2b + 1); } /** Writes a natural number in Golomb coding. * *

Golomb coding with modulo b writes a natural number x as the quotient of * the division of x and b in {@linkplain #writeUnary(int) unary}, * followed by the remainder in {@linkplain #writeMinimalBinary(int, int) minimal binary code}. * *

This method implements also the case in which b is 0: in this case, * the argument x may only be zero, and nothing will be written. * * @param x a natural number. * @param b the modulus for the coding. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number or use a negative modulus. */ public int writeGolomb(final int x, final int b) throws IOException { return writeGolomb(x, b, Fast.mostSignificantBit(b)); } /** Writes a natural number in Golomb coding. * * This method is faster than {@link #writeGolomb(int,int)} because it does not * have to compute log2b. * * @param x a natural number. * @param b the modulus for the coding. * @param log2b the floor of the base-2 logarithm of the coding modulus (it is irrelevant when b is zero). * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number or use a negative modulus. * @see #writeGolomb(int, int) */ public int writeGolomb(final int x, final int b, final int log2b) throws IOException { if (x < 0) throw new IllegalArgumentException("The argument " + x + " is negative"); if (b < 0) throw new IllegalArgumentException("The modulus " + b + " is negative"); if (b == 0) { if (x != 0) throw new IllegalArgumentException("The modulus is 0, but the argument is " + x); return 0; } final int l = writeUnary(x / b); // The remainder to be encoded. return l + writeMinimalBinary(x % b, b, log2b); } /** Writes a long natural number in Golomb coding. * * @param x a long natural number. * @param b the modulus for the coding. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number or use a negative modulus. * @see #writeGolomb(int, int) */ public long writeLongGolomb(final long x, final long b) throws IOException { return writeLongGolomb(x, b, Fast.mostSignificantBit(b)); } /** Writes a long natural number in Golomb coding. * * This method is faster than {@link #writeLongGolomb(long,long)} because it does not * have to compute log2b. * * @param x a long natural number. * @param b the modulus for the coding. * @param log2b the floor of the base-2 logarithm of the coding modulus (it is irrelevant when b is zero). * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number or use a negative modulus. * @see #writeGolomb(int, int) */ public long writeLongGolomb(final long x, final long b, final int log2b) throws IOException { if (x < 0) throw new IllegalArgumentException("The argument " + x + " is negative"); if (b < 0) throw new IllegalArgumentException("The modulus " + b + " is negative"); if (b == 0) { if (x != 0) throw new IllegalArgumentException("The modulus is 0, but the argument is " + x); return 0; } final long l = writeLongUnary(x / b); // The remainder to be encoded. return l + writeLongMinimalBinary(x % b, b, log2b); } /** Writes a natural number in skewed Golomb coding. * *

This method implements also the case in which b is 0: in this case, * the argument x may only be zero, and nothing will be written. * * @param x a natural number. * @param b the modulus for the coding. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number or use a negative modulus. */ public int writeSkewedGolomb(final int x, final int b) throws IOException { if (x < 0) throw new IllegalArgumentException("The argument " + x + " is negative"); if (b < 0) throw new IllegalArgumentException("The modulus " + b + " is negative"); if (b == 0) { if (x != 0) throw new IllegalArgumentException("The modulus is 0, but the argument is " + x); return 0; } final int i = Fast.mostSignificantBit(x / b + 1); final int l = writeUnary(i); final int M = ((1 << i + 1) - 1) * b; final int m = (M / (2 * b)) * b; return l + writeMinimalBinary(x - m, M - m); } /** Writes a long natural number in skewed Golomb coding. * *

This method implements also the case in which b is 0: in this case, * the argument x may only be zero, and nothing will be written. * * @param x a long natural number. * @param b the modulus for the coding. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number or use a negative modulus. * @see #writeSkewedGolomb(int, int) */ public long writeLongSkewedGolomb(final long x, final long b) throws IOException { if (x < 0) throw new IllegalArgumentException("The argument " + x + " is negative"); if (b < 0) throw new IllegalArgumentException("The modulus " + b + " is negative"); if (b == 0) { if (x != 0) throw new IllegalArgumentException("The modulus is 0, but the argument is " + x); return 0; } final long i = Fast.mostSignificantBit(x / b + 1); final long l = writeLongUnary(i); final long M = ((1L << i + 1) - 1) * b; final long m = (M / (2 * b)) * b; return l + writeLongMinimalBinary(x - m, M - m); } /** Writes a natural number in ζ coding. * *

ζ coding (with modulo k) records positive numbers in * the intervals * [1,2k-1],[2k,2k+1-1],…,[2hk,2(h+1)k-1] * by coding h in unary, followed by a minimal binary coding of * the offset in the interval. The coding of a natural number is obtained * by adding one and coding. * *

ζ codes were defined by * Paolo Boldi and Sebastiano Vigna in * “Codes for the World−Wide Web”, * Internet Math., 2(4):405-427, 2005. The paper contains also a detailed analysis. * * @param x a natural number. * @param k the shrinking factor. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number or use a nonpositive shrinking factor. */ public int writeZeta(int x, final int k) throws IOException { if (x < 0) throw new IllegalArgumentException("The argument " + x + " is negative"); if (k < 1) throw new IllegalArgumentException("The shrinking factor " + k + " is not positive"); if (k == 3 && x < MAX_PRECOMPUTED) return writeInt(ZETA_3[x], ZETA_3[x] >>> 26); final int msb = Fast.mostSignificantBit(++x); final int h = msb / k; final int l = writeUnary(h); final int left = 1 << h * k; return l + (x - left < left ? writeInt(x - left, h * k + k - 1) : writeInt(x, h * k + k)); } /** Writes a long natural number in ζ coding. * * @param x a long natural number. * @param k the shrinking factor. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number or use a nonpositive shrinking factor. * @see #writeZeta(int, int) */ public int writeLongZeta(long x, final int k) throws IOException { if (x < 0) throw new IllegalArgumentException("The argument " + x + " is negative"); if (k < 1) throw new IllegalArgumentException("The shrinking factor " + k + " is not positive"); if (k == 3 && x < MAX_PRECOMPUTED) return writeInt(ZETA_3[(int)x], ZETA_3[(int)x] >>> 26); final int msb = Fast.mostSignificantBit(++x); final int h = msb / k; final int l = writeUnary(h); final long left = 1L << h * k; return l + (x - left < left ? writeLong(x - left, h * k + k - 1) : writeLong(x, h * k + k)); } /** Writes a natural number in variable-length nibble coding. * *

Variable-length nibble coding records a natural number by padding its binary * representation to the left using zeroes, until its length is a multiple of three. * Then, the resulting string is * broken in blocks of 3 bits, and each block is prefixed with a bit, which is * zero for all blocks except for the last one. * @param x a natural number. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number. */ public int writeNibble(final int x) throws IOException { if (x < 0) throw new IllegalArgumentException("The argument " + x + " is negative"); if (x == 0) return writeInt(8, 4); final int msb = Fast.mostSignificantBit(x); int h = msb / 3; do { writeBit(h == 0); writeInt(x >> h * 3 , 3); } while(h-- != 0); return ((msb / 3) + 1) << 2; } /** Writes a long natural number in variable-length nibble coding. * * @param x a long natural number. * @return the number of bits written. * @throws IllegalArgumentException if you try to write a negative number. * @see #writeNibble(int) */ public int writeLongNibble(final long x) throws IOException { if (x < 0) throw new IllegalArgumentException("The argument " + x + " is negative"); if (x == 0) return writeInt(8, 4); final int msb = Fast.mostSignificantBit(x); int h = msb / 3; do { writeBit(h == 0); writeInt((int)(x >> h * 3) , 3); } while(h-- != 0); return ((msb / 3) + 1) << 2; } /** Copies a given number of bits from a given input bit stream into this output bit stream. * * @param ibs an input bit stream. * @param length the number of bits to copy. * @throws EOFException if there are not enough bits to copy. */ public void copyFrom(final InputBitStream ibs, 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); ibs.read(buffer, toRead); write(buffer, 0, toRead); length -= toRead; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy