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

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

package it.unimi.dsi.io;

/*
 * 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.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