Java IO provides two primary types of streams to handle input and output operations: byte streams and character streams. Understanding the distinction between these two is crucial for selecting the right tools when reading or writing data in a Java program.
Byte streams are designed to handle raw binary data β such as images, audio files, or serialized objects. These streams read and write 8-bit bytes, making them suitable for any kind of binary input or output.
Java provides the following abstract base classes for byte streams:
InputStream
(for reading)OutputStream
(for writing)All byte stream classes in Java are derived from these.
.jpg
, .png
)import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamExample {
public static void main(String[] args) {
try (
// Open input and output streams for binary file
FileInputStream input = new FileInputStream("input.jpg");
FileOutputStream output = new FileOutputStream("output.jpg");
) {
int data;
// Read and write one byte at a time
while ((data = input.read()) != -1) {
output.write(data);
}
System.out.println("File copied successfully using byte streams.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Explanation:
.jpg
file byte-by-byte and writes it to another file.Character streams are designed to handle text data, dealing with 16-bit Unicode characters. These streams automatically handle character encoding and decoding, making them ideal for reading and writing text files.
Java provides the following abstract base classes for character streams:
Reader
(for reading characters)Writer
(for writing characters)These streams are encoding-aware and useful when working with text in any language.
.txt
, .csv
, .xml
, and other plain-text filesimport java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CharacterStreamExample {
public static void main(String[] args) {
try (
// Open character streams for text file
FileReader reader = new FileReader("input.txt");
FileWriter writer = new FileWriter("output.txt");
) {
int ch;
// Read and write one character at a time
while ((ch = reader.read()) != -1) {
writer.write(ch);
}
System.out.println("File copied successfully using character streams.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Explanation:
input.txt
to output.txt
using character streams.Feature | Byte Streams | Character Streams |
---|---|---|
Base Classes | InputStream / OutputStream |
Reader / Writer |
Data Type | Bytes (8-bit) | Characters (16-bit Unicode) |
Suitable For | Binary data (images, files) | Text data (logs, documents) |
Encoding Awareness | Not aware of encoding | Automatically handles encoding |
Performance (text) | May corrupt text without encoding | Ideal for reading/writing text |
Common Subclasses | FileInputStream , BufferedInputStream |
FileReader , BufferedReader |
For example:
FileInputStream
to copy a binary .pdf
file.FileReader
to read a .txt
file containing human-readable content.Javaβs stream architecture cleanly separates IO into byte streams and character streams, each tailored to specific types of data. Byte streams provide raw access to data, making them perfect for binary files, while character streams add encoding support, making them safer and more efficient for text.
By choosing the appropriate stream type, developers can avoid common bugs such as text corruption and improve the performance and maintainability of their applications. Understanding this distinction is a foundational skill for mastering Java IO.
In Javaβs IO system, the InputStream
and OutputStream
classes form the foundation for all byte stream input and output operations. They enable reading and writing of binary data, such as images, audio files, PDF documents, or raw network data.
These two abstract classes reside in the java.io
package and define the core API for handling low-level byte IO. All other byte-based stream classes in Java either extend or decorate these two base classes.
InputStream
is an abstract class that provides methods to read one byte at a time, or an array of bytes, from a source such as a file, socket, or byte array.
Method | Description |
---|---|
int read() |
Reads one byte of data and returns it as an int (0β255), or -1 if end of stream is reached. |
int read(byte[] b) |
Reads bytes into the provided array. |
int read(byte[] b, int off, int len) |
Reads up to len bytes into array b , starting at offset off . |
void close() |
Closes the stream and releases any resources. |
int available() |
Returns the number of bytes that can be read without blocking. |
FileInputStream
β Reads from a file.BufferedInputStream
β Adds buffering to reduce IO calls.ByteArrayInputStream
β Reads from a byte array.ObjectInputStream
β Reads serialized Java objects.PipedInputStream
β Reads from a connected PipedOutputStream (used in thread communication).import java.io.FileInputStream;
import java.io.IOException;
public class InputStreamExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("input.dat")) {
int byteData;
// Read one byte at a time until end of file
while ((byteData = fis.read()) != -1) {
System.out.print((char) byteData); // Print byte as character (for demo)
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Note: This approach works best for small files. For larger files, use buffered streams.
OutputStream
is the abstract superclass for all classes that write raw bytes to an output destination, such as a file, byte array, or network socket.
Method | Description |
---|---|
void write(int b) |
Writes the specified byte (lower 8 bits of int). |
void write(byte[] b) |
Writes all bytes from the given array. |
void write(byte[] b, int off, int len) |
Writes len bytes from the array starting at offset off . |
void flush() |
Forces any buffered output bytes to be written out. |
void close() |
Closes the stream and releases resources. |
FileOutputStream
β Writes to a file.BufferedOutputStream
β Adds buffering to improve performance.ByteArrayOutputStream
β Writes to an internal byte array.ObjectOutputStream
β Writes serialized objects.PipedOutputStream
β Connects to a PipedInputStream
.import java.io.FileOutputStream;
import java.io.IOException;
public class OutputStreamExample {
public static void main(String[] args) {
String message = "Hello, this is a test message!";
try (FileOutputStream fos = new FileOutputStream("output.dat")) {
// Convert the string to bytes and write to file
fos.write(message.getBytes());
System.out.println("Message written to file.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Explanation: getBytes()
converts the string to a byte array, which is then written to the file.
Hereβs a practical example that demonstrates using both InputStream
and OutputStream
to copy the contents of a binary file.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopyExample {
public static void main(String[] args) {
try (
FileInputStream input = new FileInputStream("source.dat");
FileOutputStream output = new FileOutputStream("copy.dat");
) {
byte[] buffer = new byte[1024]; // 1KB buffer
int bytesRead;
// Read from source and write to destination
while ((bytesRead = input.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
System.out.println("File copied successfully.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Explanation:
BufferedInputStream
and BufferedOutputStream
to reduce disk access.available()
carefully; it provides an estimate, not a guarantee.IOException
or let it propagate.InputStream
and OutputStream
are the backbone of Javaβs byte stream IO system. They provide a flexible, low-level interface for reading and writing binary data across a variety of sources and destinations. By understanding these classes and their common subclasses, you gain the tools necessary to handle a wide range of IO tasks β from simple file operations to complex network data processing. Mastering them sets the foundation for efficient, reliable IO handling in any Java application.
Java provides a distinct set of stream classes for character-based input and output, built around the Reader
and Writer
abstract classes. These classes were introduced to support Unicode and allow seamless handling of text data in a platform- and encoding-independent manner.
While InputStream
and OutputStream
are designed for byte-level operations, Reader
and Writer
operate at the character level, abstracting away byte-to-character conversions and making it easier to work with textual content.
Early Java IO relied solely on byte streams (InputStream
and OutputStream
). However, byte streams arenβt encoding-aware β they deal with raw bytes, so developers had to manually convert bytes to characters, which led to common bugs and encoding issues.
To resolve this, the Java platform introduced Reader
and Writer
, which:
Reader
is an abstract base class for reading character streams. It reads 16-bit Unicode characters from text sources such as files, memory, or network streams.
Method | Description |
---|---|
int read() |
Reads a single character, returns -1 if end of stream. |
int read(char[] cbuf) |
Reads characters into an array. |
int read(char[] cbuf, int off, int len) |
Reads up to len characters into cbuf starting at off . |
void close() |
Closes the stream. |
boolean ready() |
Checks if the stream is ready to be read. |
FileReader
β Reads characters from a file.BufferedReader
β Adds buffering and line-based reading.InputStreamReader
β Bridges byte streams to character streams with encoding support.CharArrayReader
β Reads from a character array.StringReader
β Reads from a string.Writer
is the abstract superclass for writing character streams. It writes characters, arrays, or strings to text destinations.
Method | Description |
---|---|
void write(int c) |
Writes a single character. |
void write(char[] cbuf) |
Writes an array of characters. |
void write(String str) |
Writes a string. |
void flush() |
Flushes the stream (forces buffered data to be written). |
void close() |
Closes the stream. |
FileWriter
β Writes characters to a file.BufferedWriter
β Buffers characters to improve performance.OutputStreamWriter
β Converts characters into bytes using specified encoding.CharArrayWriter
β Writes to a character array in memory.StringWriter
β Writes to a string buffer.import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class ReaderExample {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("example.txt"))) {
String line;
// Read line by line until end of file
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Explanation:
BufferedReader
wraps FileReader
to provide efficient, line-based reading.import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class WriterExample {
public static void main(String[] args) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
writer.write("Hello, world!");
writer.newLine();
writer.write("This was written using character streams.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Explanation:
BufferedWriter
improves performance by reducing direct disk writes.newLine()
writes the system-dependent line separator.Character streams like FileReader
and FileWriter
use the platform's default encoding, which can vary. To specify an encoding (e.g., UTF-8), use bridging streams like InputStreamReader
and OutputStreamWriter
.
import java.io.*;
public class UTF8ReadExample {
public static void main(String[] args) {
try (
InputStreamReader isr = new InputStreamReader(new FileInputStream("utf8.txt"), "UTF-8");
BufferedReader reader = new BufferedReader(isr)
) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
import java.io.*;
public class UTF8WriteExample {
public static void main(String[] args) {
try (
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("utf8-output.txt"), "UTF-8");
BufferedWriter writer = new BufferedWriter(osw)
) {
writer.write("γγγ«γ‘γ―γδΈηοΌ"); // Japanese: "Hello, World!"
} catch (IOException e) {
e.printStackTrace();
}
}
}
Note: Always specify character encoding explicitly for portable, correct international text handling.
Feature | Byte Streams (InputStream /OutputStream ) |
Character Streams (Reader /Writer ) |
---|---|---|
Data Type | Bytes (8-bit) | Characters (16-bit Unicode) |
Encoding Awareness | Not aware | Handles character encoding/decoding |
Best For | Binary files (images, audio, PDFs) | Text files, source code, logs, CSVs |
Example Classes | FileInputStream , BufferedOutputStream |
BufferedReader , FileWriter , PrintWriter |
The Reader
and Writer
classes bring structure and clarity to handling character data in Java. They solve the encoding challenges present in byte streams and allow developers to work naturally with Unicode and multi-language text. By choosing character streams over byte streams for text-based operations, Java developers can write more robust, maintainable, and internationalization-friendly code.
When working with files and directories in Java, the primary tool is the java.io.File
class. Unlike input/output stream classes, File
does not handle reading or writing the contents of a fileβit represents the abstract path of a file or directory in the file system and allows you to manipulate metadata and perform file-level operations such as creating, deleting, renaming, or checking properties.
File
ClassThe File
class represents both files and directories. It is used to:
To use it, you simply create an instance of File
by passing a pathname (as a String
or Path
):
import java.io.File;
File myFile = new File("example.txt");
This line does not create a physical file. It only creates a representation of the path. The actual file operations require calling methods on the File
object.
To create a new empty file, use the createNewFile()
method:
import java.io.File;
import java.io.IOException;
public class CreateFileExample {
public static void main(String[] args) {
File file = new File("sample.txt");
try {
if (file.createNewFile()) {
System.out.println("File created: " + file.getName());
} else {
System.out.println("File already exists.");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
createNewFile()
returns true
if the file was created, false
if it already exists.IOException
if an error occurs (e.g., permissions issue).To create a directory, use mkdir()
:
File dir = new File("myFolder");
if (dir.mkdir()) {
System.out.println("Directory created.");
} else {
System.out.println("Failed to create directory.");
}
To create nested directories, use mkdirs()
:
File nestedDir = new File("parent/child/grandchild");
nestedDir.mkdirs(); // creates all intermediate directories if needed
To delete a file or empty directory, use the delete()
method:
File file = new File("sample.txt");
if (file.delete()) {
System.out.println("Deleted the file: " + file.getName());
} else {
System.out.println("Failed to delete the file.");
}
Important:
delete()
does not delete non-empty directories.File
class; this must be implemented manually or by using java.nio.file.Files
.To rename or move a file, use renameTo()
:
File oldFile = new File("sample.txt");
File newFile = new File("renamed.txt");
if (oldFile.renameTo(newFile)) {
System.out.println("File renamed successfully.");
} else {
System.out.println("Rename failed.");
}
Note: This method can also move a file to a different directory.
The File
class provides several methods to inspect the file or directory:
import java.io.File;
public class Test {
public static void main(String[] argv) throws Exception {
File file = new File("example.txt");
System.out.println("File name: " + file.getName());
System.out.println("Absolute path: " + file.getAbsolutePath());
System.out.println("Parent: " + file.getParent());
System.out.println("Exists: " + file.exists());
System.out.println("Is directory: " + file.isDirectory());
System.out.println("Is file: " + file.isFile());
System.out.println("Readable: " + file.canRead());
System.out.println("Writable: " + file.canWrite());
System.out.println("Executable: " + file.canExecute());
System.out.println("File size (bytes): " + file.length());
}
}
These methods allow you to verify if a file exists, distinguish between files and directories, and check permissions.
To list files inside a directory, use list()
or listFiles()
:
import java.io.File;
public class Test {
public static void main(String[] argv) throws Exception {
File directory = new File("myFolder");
if (directory.isDirectory()) {
String[] fileNames = directory.list();
for (String name : fileNames) {
System.out.println(name);
}
}
}
}
Or get File
objects:
import java.io.File;
public class Test {
public static void main(String[] argv) throws Exception {
File directory = new File("myFolder");
File[] files = directory.listFiles();
for (File f : files) {
System.out.println(f.getName() + (f.isDirectory() ? " (dir)" : " (file)"));
}
}
}
import java.io.File;
public class Test {
public static void main(String[] argv) throws Exception {
File relative = new File("sample.txt");
File absolute = new File("/home/user/docs/sample.txt");
System.out.println("Relative path: " + relative.getPath());
System.out.println("Absolute path: " + absolute.getAbsolutePath());
}
}
File
MethodsMethod | Purpose |
---|---|
createNewFile() |
Creates a new file |
mkdir() / mkdirs() |
Creates directories |
delete() |
Deletes a file or empty directory |
renameTo(File) |
Renames or moves a file |
exists() |
Checks existence |
isFile() / isDirectory() |
Checks file type |
canRead() / canWrite() / canExecute() |
Checks permissions |
length() |
Gets file size in bytes |
list() / listFiles() |
Lists files in a directory |
The File
class in Java IO provides powerful tools to interact with the file system, enabling developers to create, delete, inspect, and manipulate files and directories. Although it doesn't handle file content, it is essential for managing file paths and metadata. For actual content processing, Reader
/Writer
or InputStream
/OutputStream
classes are used in conjunction. Mastering the File
class sets the foundation for reliable and efficient file handling in Java applications.
In Java IO, reading and writing data directly to and from a source (like a file or socket) using unbuffered streams (e.g., FileInputStream
, FileOutputStream
) can be inefficient, especially when data is processed byte-by-byte or character-by-character. Buffered streams were introduced to solve this performance issue by minimizing the number of expensive disk or network access operations through the use of an in-memory buffer.
Buffering refers to the technique of using a temporary memory areaβa bufferβto store data before it's read or written. Instead of performing a system-level read/write operation for every byte or character, a buffered stream:
This reduces the number of IO operations, improving performance dramatically.
Java provides four main buffered stream classes:
Buffered Stream Class | Base Class | Type |
---|---|---|
BufferedInputStream |
InputStream |
Byte-based input |
BufferedOutputStream |
OutputStream |
Byte-based output |
BufferedReader |
Reader |
Character-based input |
BufferedWriter |
Writer |
Character-based output |
BufferedInputStream
wraps an existing InputStream
and reads a block of bytes (default 8192 bytes) into an internal buffer. When your program reads from the stream, it accesses data from the buffer, not the file or socket directlyβunless the buffer is empty.
import java.io.*;
public class BufferedInputExample {
public static void main(String[] args) {
try (
FileInputStream fis = new FileInputStream("input.txt");
BufferedInputStream bis = new BufferedInputStream(fis)
) {
int byteData;
while ((byteData = bis.read()) != -1) {
System.out.print((char) byteData); // Convert byte to char
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Why it's efficient: Instead of one disk read per byte, a chunk of data is loaded at once, then served byte-by-byte from memory.
BufferedOutputStream
collects bytes in a memory buffer and writes them in chunks. This avoids frequent, slow writes to disk or a network stream.
import java.io.*;
public class BufferedOutputExample {
public static void main(String[] args) {
try (
FileOutputStream fos = new FileOutputStream("output.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos)
) {
String message = "Buffered output stream example in Java.";
bos.write(message.getBytes());
bos.flush(); // Ensure data is written from buffer to file
} catch (IOException e) {
e.printStackTrace();
}
}
}
Important: Always call flush()
before closing to ensure remaining data in the buffer is written.
BufferedReader
reads characters efficiently by wrapping a Reader
(e.g., FileReader
or InputStreamReader
). It also provides convenient methods like readLine()
, which are not available in unbuffered readers.
import java.io.*;
public class BufferedReaderExample {
public static void main(String[] args) {
try (
BufferedReader reader = new BufferedReader(new FileReader("notes.txt"))
) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line); // Print each line of the file
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Benefits:
BufferedWriter
buffers character data and writes it in bulk. It also includes a newLine()
method to write platform-specific line separators.
import java.io.*;
public class BufferedWriterExample {
public static void main(String[] args) {
try (
BufferedWriter writer = new BufferedWriter(new FileWriter("log.txt"))
) {
writer.write("BufferedWriter is efficient for writing text.");
writer.newLine();
writer.write("It reduces the number of write operations.");
writer.flush(); // Push remaining data to file
} catch (IOException e) {
e.printStackTrace();
}
}
}
Note: Like BufferedOutputStream
, always flush the writer to avoid data loss.
Buffered streams are especially useful when:
By default, Java uses an 8 KB (8192 bytes) buffer for buffered streams. You can specify a different size:
import java.io.BufferedInputStream;
import java.io.FileInputStream;
public class Test {
public static void main(String[] argv) throws Exception {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("data.bin"), 16384); // 16 KB buffer
}
}
Custom buffer sizes can optimize performance depending on the data volume and system architecture.
Buffered streams in Java are vital for efficient IO performance. By reading and writing data in blocks rather than byte-by-byte or character-by-character, they reduce the number of costly interactions with the file system or network. Whether you're dealing with binary or text data, buffered streams offer higher speed, lower latency, and improved application responsiveness. As a best practice, always prefer buffered streams unless working with very small data or special cases where buffering is unnecessary.
Javaβs standard input and output streams (InputStream
and OutputStream
) operate at the byte level and lack the ability to directly read or write Java primitive data types (like int
, float
, or boolean
). To bridge this gap, Java provides data streams, specifically DataInputStream
and DataOutputStream
, which enable applications to read and write Java primitives and strings in a platform-independent and efficient way.
Data streams offer:
int
, float
, long
, boolean
, double
, etc.InputStream
/OutputStream
: These classes wrap around byte streams, extending their capabilities.This makes them ideal for saving and restoring structured binary data in a format that can later be decoded accuratelyβwithout manual byte parsing.
DataOutputStream
extends FilterOutputStream
and provides methods to write Java primitives in a standardized binary format.
Method | Description |
---|---|
writeInt(int v) |
Writes 4 bytes for an int |
writeDouble(double v) |
Writes 8 bytes for a double |
writeBoolean(boolean v) |
Writes 1 byte (0 or 1 ) |
writeUTF(String s) |
Writes a string in modified UTF-8 format |
writeChar(int v) |
Writes 2 bytes for a char |
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.bin"));
DataInputStream
extends FilterInputStream
and complements DataOutputStream
by reading data in the same format it was written.
Method | Description |
---|---|
readInt() |
Reads 4 bytes and returns an int |
readDouble() |
Reads 8 bytes and returns a double |
readBoolean() |
Reads 1 byte and returns boolean |
readUTF() |
Reads a string in modified UTF-8 |
readChar() |
Reads 2 bytes and returns a char |
DataInputStream dis = new DataInputStream(new FileInputStream("data.bin"));
Important: The order in which you read data must exactly match the order it was written.
import java.io.*;
public class DataOutputExample {
public static void main(String[] args) {
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.bin"))) {
dos.writeInt(42); // 4 bytes
dos.writeDouble(3.14159); // 8 bytes
dos.writeBoolean(true); // 1 byte
dos.writeUTF("Hello, Java"); // String with length prefix
dos.writeChar('J'); // 2 bytes
System.out.println("Data written to file.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Explanation:
writeUTF
prepends the string with its length in bytes, encoded in modified UTF-8.import java.io.*;
public class DataInputExample {
public static void main(String[] args) {
try (DataInputStream dis = new DataInputStream(new FileInputStream("data.bin"))) {
int intValue = dis.readInt();
double doubleValue = dis.readDouble();
boolean boolValue = dis.readBoolean();
String strValue = dis.readUTF();
char charValue = dis.readChar();
System.out.println("Read values:");
System.out.println("Int: " + intValue);
System.out.println("Double: " + doubleValue);
System.out.println("Boolean: " + boolValue);
System.out.println("String: " + strValue);
System.out.println("Char: " + charValue);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Explanation:
EOFException
).Data streams can throw several checked exceptions:
IOException
: General IO issues (file not found, access denied, etc.).EOFException
: Reached the end of file unexpectedly while reading.Always wrap stream operations in try-with-resources
to ensure automatic resource management.
DataOutputStream
can only be reliably read with DataInputStream
(or a decoder that understands the format).writeUTF()
is not suitable for storing very large strings (limit: 65,535 bytes).BufferedReader
, BufferedWriter
) unless binary encoding is required.DataInputStream
and DataOutputStream
are powerful tools for binary IO in Java. They eliminate the complexity of manually converting primitives to and from byte arrays, ensuring accurate and portable storage of Javaβs basic data types. Whether you're building a low-level file format or communicating over sockets, data streams provide a clean, efficient, and consistent way to serialize and deserialize primitive values.