Skip to main content

Byte Streams

Byte Streams are the foundation of Java I/O. They are used to read and write raw, binary data (8-bit bytes), making them ideal for handling files like images, audio files, video files, database files, and network packets.

All byte stream classes are descendant subclasses of the abstract classes java.io.InputStream and java.io.OutputStream.


InputStream and OutputStream

These two abstract classes define the core API for reading and writing bytes:

Core Methods of InputStream

  • int read(): Reads the next byte of data from the input stream. Returns the byte as an integer value in the range 0 to 255, or -1 if the end of the stream is reached.
  • int read(byte[] b): Reads up to b.length bytes of data into an array of bytes. Returns the total number of bytes read, or -1 if there is no more data.
  • void close(): Closes the input stream and releases any system resources associated with it.

Core Methods of OutputStream

  • void write(int b): Writes the specified byte to the output stream.
  • void write(byte[] b): Writes b.length bytes from the specified byte array to the output stream.
  • void flush(): Flushes the output stream and forces any buffered output bytes to be written out.
  • void close(): Closes the output stream and releases any system resources.

FileInputStream and FileOutputStream

These are concrete implementations of InputStream and OutputStream designed to read and write files on the local filesystem.


Proper Resource Management: Try-With-Resources

I/O operations interact directly with the operating system's file handles. If you do not close these streams, it can cause resource leaks, which can lead to system crashes or lock-up files.

The Old Way (Pre-Java 7): try-catch-finally

Historically, you had to close streams inside a finally block. This resulted in verbose and messy boilerplate code:

FileInputStream in = null;
try {
in = new FileInputStream("input.dat");
// read data
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

The Modern Way (Java 7+): Try-With-Resources

The try-with-resources statement automatically closes any class implementing the java.lang.AutoCloseable interface (which includes all Java I/O streams) at the end of the statement, even if an exception occurs.

try (FileInputStream in = new FileInputStream("input.dat")) {
// read data
// 'in' is automatically closed here
} catch (IOException e) {
e.printStackTrace();
}

Practical Code Example: Binary File Copier

The following example shows how to copy a binary file (such as a JPEG image) from one location to another.

Rather than copying byte-by-byte (which is extremely slow due to frequent disk/OS interactions), we read data into a temporary buffer array (byte[] buffer) to perform block operations.

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class ImageCopier {
public static void main(String[] args) {
String sourceFile = "input_image.jpg";
String destFile = "copied_image.jpg";

long startTime = System.currentTimeMillis();

// Use try-with-resources to manage both input and output streams
try (FileInputStream fis = new FileInputStream(sourceFile);
FileOutputStream fos = new FileOutputStream(destFile)) {

// Buffer array to hold 4KB of data at a time
byte[] buffer = new byte[4096];
int bytesRead;

// read(buffer) returns the actual number of bytes read, or -1 at EOF
while ((bytesRead = fis.read(buffer)) != -1) {
// Write only the actual bytes read into the output file
fos.write(buffer, 0, bytesRead);
}

long endTime = System.currentTimeMillis();
System.out.println("File copy completed successfully!");
System.out.println("Time taken: " + (endTime - startTime) + " ms");

} catch (IOException e) {
System.err.println("Error copying file: " + e.getMessage());
e.printStackTrace();
}
}
}

Why use write(buffer, 0, bytesRead)?

It is critical to specify the offset (0) and length (bytesRead) when writing the buffer. If the last chunk read from the file is only 1,024 bytes, but we write the entire buffer (4,096 bytes), the resulting file will contain garbage bytes at the end.