Lab 5: Buffered Streams
Learning Outcomes
- Explain how a buffer is a specific type of Queue
- Develop tests that test boundary conditions
- Use an array to implement a fixed size queue in order to minimize read/write calls to the underlying operating system
- Read/write binary files
- Convert individual bits into a byte and vice versa
- Define FIFO and explain how it relates to a queue1)
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.
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:
BufferedInputStream(InputStream in)
— Creates aBufferedInputStream
object and saves its argument, the input streamin
, for later use. An internal array is also created to store the buffered input. The array must be the length of the default buffer size.BufferedInputStream(InputStream in, int size)
— Creates aBufferedInputStream
object and saves its argument, the input streamin
, for later use. An internal array is also created to store the buffered input. The array must be the length of thesize
argument. AnIllegalArgumentException
must be thrown if thesize
is not positive.readBit()
— Reads the next bit of data from the input stream. The value of the bit is returned as anint
in the range0
to1
. If no bit is available because the end of the stream has been reached, the value-1
is returned. This method blocks until input data is available, the end of the stream is detected, or an exception is thrown. Bits are returned from most to least significant. E.g., if the input stream contains one byte with the value 0b10110000, this method will return bit values in the following order: 1, 0, 1, 1, 0, 0, 0, 0. AnIOException
is propogated back if one is encountered within this method.read()
— The method should conform to the documentation for theInputStream.read()
method with the following addition: anIllegalStateException
is thrown if only a partial byte has been read from the input stream (see details below).read(byte[] b)
— The method should conform to the documentation for theInputStream.read(byte[] b)
method with the following addition: anIllegalStateException
is thrown if only a partial byte has been read from the input stream (see details below). Note that this method does not call the no-argumentread()
method of its underlying input stream.
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
readBit()
should not be called from withinread()
.read()
andread(byte[] b)
read bytes from the internal buffer. They only call the internal stream'sread(byte[] b)
method if the internal buffer is empty.- Lines 53 and 75 of
Lab5Sample.java
have the opposite logic in the conditional. They should both start withif(!Arrays.eq
... (added an exclamation point).

Buffered Output Stream
You must implement the following methods in your BufferedOutputStream
class:
BufferedOutputStream(OutputStream out)
— Creates aBufferedOutputStream
object and saves its argument, the output streamout
, for later use. An internal array is also created to store the buffered output. The array must be the length of the default buffer size.BufferedOutputStream(OutputStream out, int size)
— Creates aBufferedOutputStream
object and saves its argument, the output streamout
, for later use. An internal array is also created to store the buffered output. The array must be the length of thesize
argument. AnIllegalArgumentException
must be thrown if thesize
is not positive.writeBit(int bit)
— Writes the specified bit to this output stream. The bit to be written is the lowest-order bit of the argumentbit
. The 31 high-order bits ofbit
are ignored. E.g., if the integer passed to the method has a value of 0b11110000111100001111000011110001, all but the highlighted bit are ignored. AnIOException
is propogated back if one is encountered within this method.write(int b)
— The method should conform to the documentation for theOutputStream.write(int b)
method with the following addition: anIllegalStateException
is thrown if only a partial byte has been written to the output stream (see details below).write(byte[] b)
— The method should conform to the documentation for theOutputStream.write(byte[] b)
method with the following addition: anIllegalStateException
is thrown if only a partial byte has been written to the output stream (see details below). Note that this method does not call thewrite(int b)
method from the underlying output stream nor thewrite(int b)
method in this class.flush()
— Flushes this buffered output stream. This forces any buffered output bytes to be written out to the underlying output stream. AnIOException
is propogated back if one is encountered within this method.
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:
value >> num
— shift the bits invalue
num
bits to the right. E.g.,0b00101000 >> 2
produces0b00001010
.value << num
— shift the bits invalue
num
bits to the left. E.g.,0b00101000 << 2
produces0b10100000
.value & mask
— Bitwise AND operator. E.g.,0b001
1
1000 & 0b010
1
0101
produces0b000
1
0000
.value1 | value2
— Bitwise OR operator. E.g.,0b
0
01110
0
0 & 0b
0
10101
0
1
produces0b
0
11111
0
1
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:
- Implement the
BufferedInputStream.read()
method - Implement the
BufferedOutputStream.write()
method - Experiement with benchmarking different buffer capacities to determine what effect it has on performance
Acknowledgements
This assignment was written by Dr. Chris Taylor.