Lab 5: Buffered Streams

Learning Outcomes

Overview

In this assignment you will create your own BufferedInputStream and BufferedOutputStream classes as well as a program to test your implementations. Each class will wrap an input or output stream and use an array as a buffer to minimize the number of read()/write() calls to the wrapped input/output streams.

The BufferedInputStream and BufferedOutputStream classes described in this assignment are not the classes in the java.io package in the Java Standard Library.

Resources

Assignment Details

If no size is specified when the streams are constructed, a default buffer size of 32 bytes should be used.

Buffered Input Stream

You must implement the following methods in your BufferedInputStream class:

Note on IllegalStateException: If readBit() is called once, a partial byte (seven bits) will be left in the input stream. Once the readBit() method has been called, you must ensure that it is called seven more times before allowing any of the read() methods to be called. To ensure this, the two read() methods must throw an IllegalStateException if called when a byte from the input stream has only been partially read. I.e., readBit() has been called at least once but not a number of times that is evenly divisible by 8.

Clarifications and Tips

Reading from buffered input stream
Figure 1: Reading from BufferedInputStream

Buffered Output Stream

You must implement the following methods in your BufferedOutputStream class:

Note on IllegalStateException: If writeBit() is called once, seven additional bits will need to be written to complete the partial byte worth of data in the output stream. Once the writeBit() method has been called, you must ensure that it is called seven more times before allowing the flush() method or any of the write() methods to be called. To ensure this, you will need to modify the behavior of the flush() and two write() methods so that an IllegalStateException is thrown if called when a byte in the output stream has only been partially written. I.e., writeBit() has been called at least once but not a number of times that is evenly divisible by 8.

Both the BufferedInputStream and BufferedOutputStream classes must implement the Closable and AutoCloseable interfaces.

Testing Class

You must design and implement a Lab5OutTests class that tests your BufferedOutputStream implementations. You must use JUnit5 to implement your test class.

Dealing with Bits

In order to implement the readBit() and writeBit(int) methods, you will need to extract/insert a bit from a byte or to an integer.

The following binary operators will be useful for this:

In order to implement the readBit() method, you'll need to extract a single bit from a byte of data. A byte consists of eight bits. To get each bit from a byte, value and return it as a int, you could do this:

return value & 0b00000001;        // least significant bit
return (value >> 1) & 0b00000001; // second-least significant bit
return (value >> 2) & 0b00000001; // third-least significant bit
return (value >> 3) & 0b00000001; // fourth-least significant bit
return (value >> 4) & 0b00000001; // fourth-most significant bit
return (value >> 5) & 0b00000001; // third-most significant bit
return (value >> 6) & 0b00000001; // second-most significant bit
return (value >> 7) & 0b00000001; // most significant bit

In order to implement the writeBit(int b) method, you will need to shift the bit to be written into the appropriate location and then do a bitwise OR with the partially written byte. The first bit in a partially written byte should be located in the most significant bit position. If bit is the integer that contains the bit value to be written, you can get the value of the partially written byte by doing the following:

byte partialByte = (byte)(bit << 7);

If you already have a partially written byte, you can merge the new bit into the partially written byte by doing the following:

partialByte = (byte)((bit << 6) | partialByte); // adds second bit into partially written byte
partialByte = (byte)((bit << 5) | partialByte); // adds third bit into partially written byte
partialByte = (byte)((bit << 4) | partialByte); // adds fourth bit into partially written byte
partialByte = (byte)((bit << 3) | partialByte); // adds fifth bit into partially written byte
partialByte = (byte)((bit << 2) | partialByte); // adds sixth bit into partially written byte
partialByte = (byte)((bit << 1) | partialByte); // adds seventh bit into partially written byte
partialByte = (byte)(bit | partialByte);        // adds the last bit to the partially written so
                                                // that it is now ready to be sent to the OutputStream

Just For Fun

Movitated students may wish to consider implementing the following once they have completed the requirements for the assignment:

See your professor's instructions for details on submission guidelines and due dates.

Acknowledgements

This assignment was written by Dr. Chris Taylor.

1) Note that a buffer is essentially a fixed length queue. `BufferedInputStream.read()` is analagous to `Queue.poll()`. The `BufferedInputStream` does not have a `Queue.offer()` method. Instead, the `BufferedInputStream` does a bulk load from the `InputStream` whenever the buffer/queue is empty and `read()` is called. Similarly, `BufferOutputStream.write()` is analagous to `Queue.offer()`. Instead of having a `Queue.offer()` method, the `BufferedOutputStream` does a bulk flush to the `OutputStream` when the buffer/queue is filled.