Last active
August 6, 2017 17:15
-
-
Save camertron/927115e5a2f43f27f4eb5801d87d2964 to your computer and use it in GitHub Desktop.
Java classes for reading and writing streams of bits.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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