Work with byte streams (InputStream, OutputStream) and character streams (Reader, Writer).
Java's classic I/O model, found in the `java.io` package, is built upon the concept of streams. A stream is a sequence of data flowing from a source to a destination. There are two main hierarchies of streams: byte streams and character streams. Byte streams are used for reading and writing binary data (sequences of 8-bit bytes). The two main abstract classes for byte streams are `InputStream` and `OutputStream`. Concrete implementations include `FileInputStream` and `FileOutputStream` for working with files, which handle raw byte data perfect for images, executables, or any non-textual data. Character streams are used for reading and writing character data (sequences of 16-bit Unicode characters). The main abstract classes are `Reader` and `Writer`. These automatically handle the conversion between bytes and characters based on a specified character encoding. This makes them ideal for working with text files. Concrete implementations include `FileReader` and `FileWriter`. A common and powerful technique is to wrap or 'decorate' streams to add more functionality. For example, you can wrap a `FileInputStream` in an `InputStreamReader` to convert it from a byte stream to a character stream, and then wrap that in a `BufferedReader`. The `BufferedReader` reads text from the character stream, buffering the characters to provide for the efficient reading of characters, arrays, and lines, using its `readLine()` method. This layering approach is a key design pattern in the `java.io` library.