Skip to content

Instantly share code, notes, and snippets.

@camertron
Last active August 6, 2017 17:15
Show Gist options
  • Select an option

  • Save camertron/927115e5a2f43f27f4eb5801d87d2964 to your computer and use it in GitHub Desktop.

Select an option

Save camertron/927115e5a2f43f27f4eb5801d87d2964 to your computer and use it in GitHub Desktop.
Java classes for reading and writing streams of bits.
import java.io.IOException;
import java.io.InputStream;
/**
* An input stream capable of reading individual bits from an underlying input stream.
*
* EN 605.421 Foundations of Algorithms
* Johns Hopkins Engineering for Professionals
* Summer 2017
*
* Author: Cameron C. Dutro
* Date: August 6th, 2017
*/
public class BitInputStream {
private static int BUFFER_SIZE = 1024; // how many bytes to read at once (disk I/O is expensive after all)
private static int BYTE_WIDTH = 8; // the number of bits in a byte
private InputStream m_stream; // the underlying stream
private byte[] m_buffer; // the byte buffer into which BUFFER_SIZE chunks are read from m_stream
private int m_byteBufferLength; // the number of bytes actually read into m_buffer
private int m_byteBufferPos; // the current position within m_buffer
private int m_bitBufferPos; // the current bit position within the current byte, m_buffer[m_byteBufferPos]
/**
* Constructs a new BitInputStream using the given (underlying) stream.
* @param stream The underlying stream to be read from. Will be used to read chunks of bytes which this class will interpret as bits.
*/
public BitInputStream(InputStream stream) {
m_stream = stream;
m_buffer = new byte[BUFFER_SIZE];
m_byteBufferPos = 0;
m_bitBufferPos = 0;
m_byteBufferLength = 0;
m_byteBufferPos = 0;
}
/**
* Reads a single bit from the stream.
* @return Returns a single bit represented as a boolean.
* @throws IOException
*/
public boolean read() throws IOException {
// if this is the first time read() is called, hydrate the byte buffer
if (m_byteBufferLength == 0) {
m_byteBufferLength = m_stream.read(m_buffer);
}
// Calculate the bit mask and use it to isolate the current bit.
// The bit mask is calculated by right-shifting a 1 bit to the current bit position, then logical ANDing it with
// the current byte to determine if the bit is flipped on or off. For example, say we care about the 3rd bit.
// Our mask would therefore be 00000100. Say the current byte is 11001101. ANDing them together yields:
//
// 11001101
// & 00000100
// --------
// 00000100
//
// Since the mask and the result are equal, the bit at position 3 must be a 1, or a boolean true.
int mask = 1 << (BYTE_WIDTH - m_bitBufferPos - 1);
boolean result = (m_buffer[m_byteBufferPos] & mask) == mask;
m_bitBufferPos ++;
// if we've extended past the width of a byte, reset the bit position and increment the byte position counter
if (m_bitBufferPos >= BYTE_WIDTH) {
m_bitBufferPos = 0;
m_byteBufferPos ++;
}
// if the current byte position exceeds what we currently have in the buffer, grab BUFFER_SIZE more bytes from
// the input stream
if (m_byteBufferPos >= m_byteBufferLength) {
m_byteBufferLength = m_stream.read(m_buffer);
m_byteBufferPos = 0;
m_bitBufferPos = 0;
}
return result;
}
/**
* Reads an entire 8-bit byte from the input stream.
* @return Returns the next 8-bit sequence from the input stream encoded as a byte.
* @throws IOException
*/
public byte readByte() throws IOException {
byte result = 0;
for (int i = 0; i < BYTE_WIDTH; i ++) {
// Read a bit and flip the corresponding result bit if true (i.e. 1). To do so, generate a mask and OR with
// the result to flip the bit at BYTE_WIDTH - i - 1. Say we want to set the bit at position 2 to 1. The mask
// would be 00000010. Say your current result byte is 00000000. ORing them together yields:
//
// 00000000
// | 00000010
// --------
// 00000010
//
// The bit at position 2 has been set to 1.
if (read()) {
result |= (1 << (BYTE_WIDTH - i - 1));
}
}
return result;
}
/**
* Reads bits into the given buffer as long as input is not exhausted. Reads a maximum of buf.length bits.
* @param buf The buffer to read bits into.
* @return Returns the number of bits actually read.
* @throws IOException
*/
public int read(boolean[] buf) throws IOException {
for (int i = 0; i < buf.length; i ++) {
// if the stream has been exhausted, bail out
if (isEos()) {
return i;
}
buf[i] = read();
}
return buf.length;
}
/**
* Determines if the underlying stream has been exhausted or not.
* @return Returns true if the stream has been exhausted, false otherwise.
*/
public boolean isEos() {
return m_byteBufferLength == -1;
}
/**
* Closes this stream, i.e. closes the underlying stream. Closed streams cannot be subsequently read from.
* @throws IOException
*/
public void close() throws IOException {
m_stream.close();
}
}
import java.io.IOException;
import java.io.OutputStream;
/**
* An output stream capable of writing individual bits to an underlying output stream.
*
* EN 605.421 Foundations of Algorithms
* Johns Hopkins Engineering for Professionals
* Summer 2017
*
* Author: Cameron C. Dutro
* Date: August 6th, 2017
*/
public class BitOutputStream {
private static int BYTE_BUFFER_SIZE = 1024; // how many bytes to write at once (disk I/O is expensive after all)
private static int BYTE_WIDTH = 8; // the number of bits in a byte
private OutputStream m_stream; // the underlying stream
private byte[] m_byteBuffer; // the byte buffer into which BUFFER_SIZE bytes are stored before being written
private int m_byteBufferPos; // the number of bytes actually contained within m_byteBuffer
private boolean[] m_bitBuffer; // the bit buffer where individual bits are stored before being added to m_byteBuffer
private int m_bitBufferPos; // the current position within m_bitBuffer
/**
* Constructs a new BitOutputStream using the given (underlying) stream.
* @param stream The underlying stream to be written to.
*/
public BitOutputStream(OutputStream stream) {
m_stream = stream;
m_byteBuffer = new byte[BYTE_BUFFER_SIZE];
m_byteBufferPos = 0;
m_bitBuffer = new boolean[BYTE_WIDTH];
m_bitBufferPos = 0;
}
/**
* Writes a single bit to the stream.
* @param bit The bit to write, represented as a boolean.
* @throws IOException
*/
public void write(boolean bit) throws IOException {
// add the bit to the bit buffer and increment the bit position counter
m_bitBuffer[m_bitBufferPos] = bit;
m_bitBufferPos ++;
// if the bit buffer contains a full byte, add it to the byte buffer and reset the bit counter
if (m_bitBufferPos == m_bitBuffer.length) {
m_byteBuffer[m_byteBufferPos] = encodeByte(m_bitBuffer);
m_byteBufferPos ++;
m_bitBufferPos = 0;
}
// if the byte buffer is full, flush it to the output stream
if (m_byteBufferPos == m_byteBuffer.length) {
flushByteBuffer();
}
}
/**
* Writes a series of bits to the stream.
* @param bits The series of bits to write.
* @throws IOException
*/
public void write(boolean[] bits) throws IOException {
for (int b = 0; b < bits.length; b ++) {
write(bits[b]);
}
}
/**
* Writes a byte to the stream. The byte is written as a series of bits so as to prevent byte alignment issues.
* @param b The byte to write.
* @throws IOException
*/
public void write(byte b) throws IOException {
for (int i = 0; i < BYTE_WIDTH; i ++) {
// Calculate the bit mask and use it to isolate the current bit.
// The bit mask is calculated by right-shifting a 1 bit to the current bit position, then logical ANDing it with
// the current byte to determine if the bit is flipped on or off. For example, say we care about the 3rd bit.
// Our mask would therefore be 00000100. Say the current byte is 11001101. ANDing them together yields:
//
// 11001101
// & 00000100
// --------
// 00000100
//
// Since the mask and the result are equal, the bit at position 3 must be a 1, or a boolean true.
int mask = 1 << (BYTE_WIDTH - i - 1);
write((b & mask) == mask);
}
}
/**
* Immediately writes all buffered bytes to the underlying output stream, then flushes the stream.
* @throws IOException
*/
public void flush() throws IOException {
flushByteBuffer();
m_stream.flush();
}
/**
* Closes the stream. If the bit buffer contains an incomplete bit, it will be right padded with zeroes and written
* to the output stream as a full byte.
* @throws IOException
*/
public void close() throws IOException {
if (m_bitBufferPos > 0) {
// right pad the last byte with zeroes
for (int i = m_bitBufferPos; i < m_bitBuffer.length; i++) {
write(false);
}
}
flush();
m_stream.close();
}
// immediately writes the contents of the byte buffer to the output stream
private void flushByteBuffer() throws IOException {
m_stream.write(m_byteBuffer, 0, m_byteBufferPos);
m_byteBufferPos = 0;
}
// converts the given array of boolean bits into a byte
// NOTE: the given boolean array must contain exactly 8 elements, although no error will be raised if it does not
private byte encodeByte(boolean[] bits) {
byte result = 0;
for (int i = 0; i < bits.length; i ++) {
// Flip the result bit if the current bit is true (i.e. 1). To do so, generate a mask and OR with the result
// to flip the bit at BYTE_WIDTH - i - 1. Say we want to set the bit at position 2 to 1. The mask would be
// 00000010. Say your current result byte is 00000000. ORing them together yields:
//
// 00000000
// | 00000010
// --------
// 00000010
//
// The bit at position 2 has been set to 1.
if (bits[i]) {
result |= (1 << (bits.length - i - 1));
}
}
return result;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment