Browse DevX
Sign up for e-mail newsletters from DevX


Create BitInputStream and BitOutputStream Classes

Although the JDK 1.4 has many Stream classes, it doesn't contain classes for reading and writing single bits. Extend your tool set by creating these BitInputStream and BitOutputStream classes.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

he classes in the java.io and java.nio packages make it easy to read and write just about any kind of data—such as bytes, or arrays of bytes. Other classes make it easy to read and write other data types; however, all these classes read and write data in pieces that are at least a byte long. For example, the DataInputStream and DataOutputStream classes can write Boolean values, which require—in theory—only a single bit to store. These classes, however, use an entire byte for each Boolean value.

In contrast, this article describes a pair of stream classes that let you read and write single bits easily. Not a bit stored as a byte, but a real bit. They treat the data stream as a stream of bits, rather than a stream of bytes. You don't have to think in terms of bytes at all.

This kind of facility is particularly useful when you are trying to save space. Some file formats store certain values using bit lengths other than 8, 16, or 32. Others—such as data compression formats—benefit from being able to forget about byte boundaries entirely, and to treat data as a homogenous stream of bits.

How BitInputStream and BitOutputStream Work
Before getting into the nitty-gritty, here's a quick glance at how you might use these classes. In many ways, they're like regular InputStream and OutputStream classes, except that they act on bits rather than bytes. Here's a program fragment that writes three bits—1, 1, and 0—to a file.

FileOutputStream fout = new FileOutputStream( "bits.dat" ); BitOutputStream bout = new BitOutputStream( fout ); bout.writeBit( 1 ); bout.writeBit( 1 ); bout.writeBit( 0 ); bout.close();

Likewise, here's some code to read them back in:

FileInputStream fin = new FileInputStream( "bits.dat" ); BitInputStream bin = new BitInputStream( fin ); int b0 = bin.readBit(); int b1 = bin.readBit(); int b2 = bin.readBit(); bin.close();

Before learning how they work, you should think a little bit about how bits are stored in files.

Creating a Bit Data Format
It's important to understand that BitInputStream and BitOutputStream don't use a special format—they use the bits contained in traditional Java streams, except that they deal with the data one bit at a time.

There is a flaw to this approach. File systems themselves deal with data in terms of bytes. Files must contain whole bytes—it's not possible to have a file that contains a partial byte. You can, of course, write a partial byte by padding the byte with zeros and writing the entire byte. The problem with this approach is that when you read the file in again, you can't distinguish between the actual data bytes and the padding. Practically speaking, this means that you can write a certain number of bits to a file—but when you read the file back in, it may contain more bits than you originally wrote out.

And it's not just files: most streams are connected, in the end, to some facility that deals with bytes, not with individual bits.

The essence of the problem is that a bit stream (a BitInputStream or BitOutputStream) is slightly more general than a byte stream (an InputStream or OutputStream). A sequence of bits can be any length; a sequence of bytes has a length, in bits, that is an even multiple of eight.

It's possible to create a special file format that could store extra information, such as the number of padding bits, which would allow perfect reconstruction of a byte stream. However, that's not the primary goal. The primary goal is to be able to view any data source—such as a file—as a stream of bits.

Doing Without Stream Inheritance
If you look at the source code in Listing 1 and Listing 2, you'll find that neither BitInputStream nor BitOutputStream are subclasses of InputStream and OutputStream. Again, that underscores the basic difference between bit streams and byte streams. Most of the methods of InputStream and OutputStream would be meaningless, or at least awkward, as part of a BitInputStream or BitOutputStream implementation.

However, the methods that the BitInputStream and BitOutputStream classes have are analogous to the methods in the traditional stream classes. As you saw in the usage examples, these classes take streams in their constructors, much like traditional stream filters:

BitOutputStream bout = new BitOutputStream( out );

Where traditional stream classes have read() or write() methods, the BitInputStream and BitOutputStream classes have readBit() and writeBit() methods.

bout.writeBit( 1 );

And, like traditional stream methods, the classes have close() methods:


Because they aren't subclasses of InputStream and OutputStream, the bit streams classes can't be used as simple replacements. But that's fine—it doesn't really make sense to try to use bit streams in places where you need byte streams.

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date