Skip to content

File I/O Streams — FileInputStream, FileOutputStream, Buffered Streams, and Data Streams

DodaTech Updated 2026-06-28 6 min read

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

  1. Not closing streams. Always use try-with-resources to auto-close. Unclosed streams leak file descriptors.
  2. Reading byte-by-byte without buffering. fis.read() per byte performs a native call each time. Always use BufferedInputStream or read into a buffer.
  3. Not calling flush() on output streams. Data may remain in the buffer. close() flushes, but if you need data written immediately, call flush().
  4. 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.
  5. Using FileInputStream for text files. For text, use FileReader with a BufferedReader to 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

What is the default buffer size for BufferedInputStream?

8192 bytes (8 KB). You can specify a custom size: new BufferedInputStream(in, 65536) for a 64 KB buffer.

What happens if I do not close a FileOutputStream?

The file handle is leaked. On Windows, the file remains locked. On Unix, the file descriptor is consumed. Eventually, too many open files causes IOException: Too many open files.

What is the difference between `FileOutputStream` and `FileWriter`?

FileOutputStream writes bytes (binary data). FileWriter writes characters (text). For text, always use Writer with a specified encoding.

Can I read a file that is being written by another process?

It depends on the OS. On Unix, processes can read files being written. On Windows, exclusive locks may prevent reading. Use RandomAccessFile for shared access.

What is the `read(byte[], int, int)` overload?

It reads up to len bytes into the buffer starting at offset off. Useful for reading into specific positions in a larger buffer.

Mini Project

Write a program FileStreamDemo.java that:

  1. Creates a binary file containing 1000 random integers using DataOutputStream
  2. Reads them back using DataInputStream and verifies the count
  3. Copies a file using a 4 KB buffer and measures the time
  4. Copies the same file byte-by-byte and compares the time
  5. Implements a simple hex dump utility that prints file contents as hex bytes with ASCII side-by-side
  6. Uses SequenceInputStream to concatenate two files
  7. Demonstrates PushbackInputStream by 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