Let’s say we want to some integers from a file. Well, we can’t use InputStream directly, but we can use FileInputStream, a class which inherits directly from InputStream. Unfortunately, FileInputStream doesn’t do any buffering. So if we want to do a lot of small reads, it may not be very efficient, because each of those reads will turn into a syscall.

Well, that’s no problem; we can use BufferedInputStream. BufferedInputStream extends FilterInputStream, which wraps one input stream in another input stream. In the case of BufferedInputStream, the outer stream does buffering.

However, we still do have a problem. BufferedInputStream doesn’t let us read integers; it only lets us read bytes and arrays of bytes. In order to get something that reads integers, we need another wrapper: DataInputStream.

So far our program looks like this:

FileInputStream fos = new FileInputStream("myfile");
BufferedInputStream bos = new BufferedInputStream(fos, 16384);
DataInputStream dos = new DataInputStream(bos);
int foo = dos.readInt();

Well, it might be a little verbose, but at least it’s safe, right? We know that whatever operation we call on our InputStream will be well-supported.
Well… not quite. One operation defined by InputStream, mark, is only supported by some InputStreams, and not others. It so happens that FileInputStream doesn’t support it (and will misbehave at runtime if you try to use it). BufferedInputStream does. Our DataInputStream does, but only because it wraps our BufferedInputStream– DataInputStream itself does not support mark.

(read the rest here)