File I/O Streams — FileInputStream, FileOutputStream, Buffered Streams, and Data Streams
In this tutorial, you will learn about File I/O Streams. We cover key concepts, practical examples, and best practices to help you master this topic.
Java I/O streams provide sequential access to data sources, with FileInputStream and FileOutputStream for binary file operations. The stream abstraction treats every data source (file, network socket, memory buffer) as a sequence of bytes, enabling uniform processing with decorator patterns.
What You'll Learn
- InputStream and OutputStream hierarchy
- FileInputStream and FileOutputStream
- Buffered streams for performance
- DataInputStream and DataOutputStream for primitives
Why It Matters
Understanding the stream decorator pattern is essential because every Java I/O operation uses it — reading a file, receiving an HTTP response, or processing a ZIP archive. Proper buffering can be 100x faster than byte-by-byte I/O.
Real-World Use
File copy utilities, image processing, network protocol implementations, and Serialization all use byte streams.
InputStream and OutputStream
The abstract base classes:
// Reading
InputStream in = new FileInputStream("input.dat");
int byteData = in.read(); // reads one byte (0-255), -1 at end
byte[] buffer = new byte[1024];
int bytesRead = in.read(buffer); // reads into buffer, returns count
in.close();
// Writing
OutputStream out = new FileOutputStream("output.dat");
out.write(byteData); // write one byte
out.write(buffer, 0, bytesRead); // write part of buffer
out.close();
FileInputStream and FileOutputStream
// Reading a file
try (FileInputStream fis = new FileInputStream("photo.jpg")) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
process(buffer, bytesRead);
}
}
// Writing a file
try (FileOutputStream fos = new FileOutputStream("output.bin")) {
byte[] data = {0x48, 0x65, 0x6C, 0x6C, 0x6F};
fos.write(data);
}
FileOutputStream Modes
// Overwrite (default)
new FileOutputStream("file.txt");
// Append mode
new FileOutputStream("file.txt", true);
Buffered Streams
Buffering reduces the number of native I/O calls by reading/writing in larger chunks:
// Without buffering — slow
try (FileInputStream fis = new FileInputStream("large.bin")) {
int singleByte;
while ((singleByte = fis.read()) != -1) {
// process one byte — thousands of native calls
}
}
// With buffering — fast
try (BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("large.bin"))) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
// process chunk — far fewer native calls
}
}
BufferedOutputStream
try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("output.bin"))) {
bos.write(data);
bos.flush(); // force write to disk
}
DataInputStream and DataOutputStream
Read/write Java primitive types in binary format:
// Writing primitives
try (DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream("data.bin")))) {
dos.writeInt(42);
dos.writeDouble(3.14159);
dos.writeUTF("Hello"); // modified UTF-8
dos.writeBoolean(true);
}
// Reading primitives
try (DataInputStream dis = new DataInputStream(
new BufferedInputStream(new FileInputStream("data.bin")))) {
int i = dis.readInt();
double d = dis.readDouble();
String s = dis.readUTF();
boolean b = dis.readBoolean();
System.out.println(i + " " + d + " " + s + " " + b);
}
Data streams are useful for saving structured binary data but are not human-readable. For text, use Reader/Writer classes.
The Decorator Pattern
The I/O stream library uses the decorator pattern — you wrap streams to add functionality:
// Add buffering AND data reading
DataInputStream dis = new DataInputStream(
new BufferedInputStream(
new FileInputStream("file.bin")));
// Add buffering AND compression
ZipInputStream zis = new ZipInputStream(
new BufferedInputStream(
new FileInputStream("archive.zip")));
Each wrapper adds a capability while maintaining the same interface.
Copying Files with Streams
public static void copyFile(String source, String dest) throws IOException {
try (InputStream in = new BufferedInputStream(new FileInputStream(source));
OutputStream out = new BufferedOutputStream(new FileOutputStream(dest))) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
}
Common Mistakes
- Not closing streams. Always use try-with-resources to auto-close. Unclosed streams leak file descriptors.
- Reading byte-by-byte without buffering.
fis.read()per byte performs a native call each time. Always useBufferedInputStreamor read into a buffer. - Not calling
flush()on output streams. Data may remain in the buffer.close()flushes, but if you need data written immediately, callflush(). - Assuming
available()returns the file size.available()returns an estimate of bytes that can be read without blocking — it is not the total file size. - Using
FileInputStreamfor text files. For text, useFileReaderwith aBufferedReaderto handle character encoding correctly.
Practice Questions
1. What is the difference between InputStream and Reader?
InputStream reads bytes. Reader reads characters. Use InputStream for binary data, Reader for text.
2. Why should you wrap a FileInputStream with a BufferedInputStream?
FileInputStream.read() performs a native (OS) call for each byte. BufferedInputStream reads a large chunk into memory and serves individual bytes from the buffer.
3. What does writeUTF() in DataOutputStream do?
It writes a string in modified UTF-8 encoding, prefixed with the length (2 bytes). Useful for cross-platform string serialization.
4. What is the purpose of flush()?
It forces any buffered bytes to be written to the underlying stream. Without flush, data may remain in the buffer.
5. Can you mark and reset on a BufferedInputStream?
Yes, BufferedInputStream supports mark() and reset() to revisit previously read data. The readlimit parameter specifies how many bytes can be read before the mark is invalidated.
Challenge Question:
Write a method Map<String, Integer> countByteOccurrences(String filePath) that reads a binary file and returns a map of each byte value (0-255) to its count. Use BufferedInputStream. Then write a method that compresses runs of identical bytes using run-length encoding (RLE): [0x00, 0x00, 0x00, 0xFF] becomes [0x00, 0x03, 0xFF, 0x01].
FAQ
Mini Project
Write a program FileStreamDemo.java that:
- Creates a binary file containing 1000 random integers using
DataOutputStream - Reads them back using
DataInputStreamand verifies the count - Copies a file using a 4 KB buffer and measures the time
- Copies the same file byte-by-byte and compares the time
- Implements a simple hex dump utility that prints file contents as hex bytes with ASCII side-by-side
- Uses
SequenceInputStreamto concatenate two files - Demonstrates
PushbackInputStreamby unreading a byte
What's Next
Byte streams handle binary data, but text requires character encoding awareness. Lesson 40 covers Readers and Writers — FileReader, FileWriter, BufferedReader, BufferedWriter, and InputStreamReader for converting between byte streams and character streams.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro