File Input Output in Java
Programs can communicate with external data and destinations, including files, network connections, and the console, thanks to Java’s core Input/Output (I/O) architecture. Streams are abstractions that can produce or consume information, and they constitute the foundation of Java’s I/O. Because the Java I/O system connects these streams to actual devices, the same I/O classes and methods can be used on a variety of device kinds. Primitives and objects are among the many data types supported by the java.io
package, which includes the majority of the classes needed for I/O operations.
Byte Streams vs. Character Streams
Byte streams and character streams are the two main forms of I/O streams that Java defines. In order to better handle Unicode characters, character streams were eventually added to Java, which at first just had byte streams.
Byte Streams
Byte streams are very helpful when working with binary data because they are made to handle the input and output of 8-bit bytes.
- In the byte stream hierarchy,
InputStream
(for input) andOutputStream
(for output) are the two abstract classes at the top. These classes define basic functions such aswrite()
andread()
. - The concrete subclasses
FileInputStream
andFileOutputStream
are frequently used for file operations. - To save up system resources and avoid “memory leaks,” all streams should be closed after use.
- When working with output streams,
flush()
is used to make sure that any buffered output is physically written to the destination.
With a try-with-resources statement that automatically closes the streams when the block finishes, this example shows how to copy a file using FileInputStream
and FileOutputStream
:
import java.io.*;
public class CopyFileBytes {
public static void main(String args[]) {
int i; // To hold each byte read
if (args.length != 2) {
System.out.println("Usage: CopyFileBytes <source_file> <destination_file>");
return;
}
// Open and manage two files via the try-with-resources statement
try (FileInputStream fin = new FileInputStream(args);
FileOutputStream fout = new FileOutputStream(args[40])) {
do {
i = fin.read(); // Reads a single byte
if (i != -1) { // -1 indicates end of file
fout.write(i); // Writes the byte
}
} while (i != -1);
System.out.println("File copied successfully using byte streams.");
} catch (FileNotFoundException e) {
System.out.println("Error: One of the files was not found: " + e.getMessage());
} catch (IOException e) {
System.out.println("I/O Error: " + e.getMessage());
}
}
}
Code Output (assuming input.txt exists with “Hello World” content):
Usage: CopyFileBytes <source_file> <destination_file>
If executed as java CopyFileBytes input.txt output.txt
:
File copied successfully using byte streams.
This would create output.txt
with the content “Hello World”.
Character Streams (Reader, Writer)
Because character streams can accommodate 16-bit Unicode character input and output, they are perfect for internationalisation and text-based input/output.
- The abstract classes
Writer
(for output) andReader
(for input) are at the top of the character stream hierarchy. Although they work with characters, they also define theread()
andwrite()
methods. - It is usual practice to utilise
FileReader
andFileWriter
for file operations. Although they convert byte streams to character streams,FileReader
andFileWriter
internally employFileInputStream
andFileOutputStream
, respectively.
Here’s an example to copy a text file using FileReader
and FileWriter
:
import java.io.*;
public class CopyFileChars {
public static void main(String args[]) {
int c; // To hold each character read
if (args.length != 2) {
System.out.println("Usage: CopyFileChars <source_file> <destination_file>");
return;
}
try (FileReader fin = new FileReader(args);
FileWriter fout = new FileWriter(args)) {
do {
c = fin.read(); // Reads a single character
if (c != -1) { // -1 indicates end of file
fout.write(c); // Writes the character
}
} while (c != -1);
System.out.println("File copied successfully using character streams.");
} catch (FileNotFoundException e) {
System.out.println("Error: One of the files was not found: " + e.getMessage());
} catch (IOException e) {
System.out.println("I/O Error: " + e.getMessage());
}
}
}
Code Output (assuming input.txt exists with “Hello World” content):
Usage: CopyFileChars <source_file> <destination_file>
If executed as java CopyFileChars input.txt output_char.txt
:
File copied successfully using character streams.
This would create output_char.txt
with the content “Hello World”.
File Management with the class
An abstract representation of files and directory pathnames is provided by the File
class in java.io
. Instead of working with streams, it describes the attributes of a file, including its directory path, time, date, and rights. Crucial features consist of:
- File and directory creation (
createNewFile()
,mkdir()
,mkdirs()
). - File deletion and search (
delete()
). - Obtaining file information (
getName()
,getPath()
,length()
,isFile()
,isDirectory()
,exists()
,canRead()
,canWrite()
).
It should be noted that for comprehensive file system operations in modern Java development (JDK 7 and later), the Path
interface and Files
class from the java.nio.file
package are the suggested substitute. This is because they provide superior support for metadata, directory traversal, and symbolic links.
import java.io.File;
import java.io.IOException;
public class FileManagementDemo {
public static void main(String[] args) {
// Create a File object representing a file
File myFile = new File("myNewFile.txt");
File myDirectory = new File("myNewDir");
try {
// Create a new file
if (myFile.createNewFile()) {
System.out.println("File created: " + myFile.getName());
} else {
System.out.println("File already exists: " + myFile.getName());
}
// Get file properties
System.out.println("Absolute path: " + myFile.getAbsolutePath());
System.out.println("Is file: " + myFile.isFile());
System.out.println("Can write: " + myFile.canWrite());
System.out.println("File size: " + myFile.length() + " bytes");
// Create a new directory
if (myDirectory.mkdir()) {
System.out.println("Directory created: " + myDirectory.getName());
} else {
System.out.println("Directory already exists or could not be created: " + myDirectory.getName());
}
} catch (IOException e) {
System.out.println("An error occurred during file operations: " + e.getMessage());
} finally {
// Clean up: delete the file and directory
if (myFile.exists()) {
myFile.delete();
System.out.println("File deleted: " + myFile.getName());
}
if (myDirectory.exists()) {
myDirectory.delete();
System.out.println("Directory deleted: " + myDirectory.getName());
}
}
}
}
Code Output (first run):
File created: myNewFile.txt
Absolute path: /path/to/your/project/myNewFile.txt
Is file: true
Can write: true
File size: 0 bytes
Directory created: myNewDir
File deleted: myNewFile.txt
Directory deleted: myNewDir
Text Input/Output
FileReader
and FileWriter
are frequently wrapped in BufferedReader
and BufferedWriter
for effective text-based file I/O.
- By buffering input,
BufferedReader
enhances efficiency and offers a handyreadLine()
method that allows you to read entire lines of text instead of individual characters. BufferedWriter
improves performance by buffering output to minimise the quantity of physical writes to the output device.
Using these classes, the following is an example of writing and reading lines from a file:
import java.io.*;
public class BufferedTextIO {
public static void main(String[] args) {
String filename = "buffered_text.txt";
String[] linesToWrite = {"First line of text.", "Second line, with more info.", "The final line."};
// Writing to a file using BufferedWriter
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
System.out.println("Writing to file '" + filename + "'...");
for (String line : linesToWrite) {
writer.write(line);
writer.newLine(); // Writes a line separator
}
writer.flush(); // Ensure all buffered data is written
System.out.println("Finished writing.");
} catch (IOException e) {
System.out.println("Error writing to file: " + e.getMessage());
}
// Reading from a file using BufferedReader
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
System.out.println("\nReading from file '" + filename + "'...");
String line;
while ((line = reader.readLine()) != null) { // Reads line by line
System.out.println("Read: " + line);
}
System.out.println("Finished reading.");
} catch (FileNotFoundException e) {
System.out.println("Error: File not found: " + filename);
} catch (IOException e) {
System.out.println("Error reading from file: " + e.getMessage());
}
}
}
Code Output:
Writing to file 'buffered_text.txt'...
Finished writing.
Reading from file 'buffered_text.txt'...
Read: First line of text.
Read: Second line, with more info.
Read: The final line.
Finished reading.
Binary Input/Output
DataInputStream
and DataOutputStream
are Java’s internal, binary formats for reading and writing primitive Java data types (such as int
, double
, and boolean
) that are not human-readable text.
DataOutputStream
implements theDataOutput
interface, providing methods likewriteInt()
,writeDouble()
,writeBoolean()
.DataInputStream
implements theDataInput
interface, with corresponding methods such asreadInt()
,readDouble()
,readBoolean()
.
import java.io.*;
public class BinaryDataIO {
public static void main(String[] args) {
String filename = "binary_data.dat";
int intValue = 12345;
double doubleValue = 987.65;
boolean booleanValue = true;
// Writing binary data
try (DataOutputStream dataOut = new DataOutputStream(new FileOutputStream(filename))) {
System.out.println("Writing binary data to '" + filename + "'...");
dataOut.writeInt(intValue);
dataOut.writeDouble(doubleValue);
dataOut.writeBoolean(booleanValue);
System.out.println("Finished writing.");
} catch (IOException e) {
System.out.println("Error writing binary data: " + e.getMessage());
}
// Reading binary data
try (DataInputStream dataIn = new DataInputStream(new FileInputStream(filename))) {
System.out.println("\nReading binary data from '" + filename + "'...");
int readInt = dataIn.readInt();
double readDouble = dataIn.readDouble();
boolean readBoolean = dataIn.readBoolean();
System.out.println("Read Integer: " + readInt);
System.out.println("Read Double: " + readDouble);
System.out.println("Read Boolean: " + readBoolean);
System.out.println("Finished reading.");
} catch (IOException e) {
System.out.println("Error reading binary data: " + e.getMessage());
}
}
}
Code Output:
Writing binary data to 'binary_data.dat'...
Finished writing.
Reading binary data from 'binary_data.dat'...
Read Integer: 12345
Read Double: 987.65
Read Boolean: true
Finished reading.
Serialization: interface
Java’s method for serialising an object’s state into a byte stream a sequence of bytes that may be sent over a network or stored in a persistent location like a file: serialization. The opposite procedure, known as deserialization, uses the byte stream to rebuild the object in memory.
The Serializable
interface must be implemented by the object’s class in order for it to be serializable. Its only function is to inform the Java Virtual Machine (JVM) that objects of this class can be serialised; as a marker interface, it does not define any methods.
ObjectOutputStream is used to serialize objects (writeObject()
), and ObjectInputStream
is used to deserialize them (readObject()
).
Instance variables declared as transient
will not be saved during serialization. Similarly, static
variables are not serialized as they belong to the class, not an object instance.
import java.io.*;
// The class must implement Serializable to be eligible for serialization
class MyObject implements Serializable {
String name;
int id;
transient String password; // This field will not be serialized
public MyObject(String name, int id, String password) {
this.name = name;
this.id = id;
this.password = password;
}
@Override
public String toString() {
return "Name: " + name + ", ID: " + id + ", Password: " + password;
}
}
public class ObjectSerializationDemo {
public static void main(String[] args) {
String filename = "object_data.ser";
MyObject obj1 = new MyObject("Alice", 101, "secret123");
// Serialize the object
try (ObjectOutputStream objOut = new ObjectOutputStream(new FileOutputStream(filename))) {
System.out.println("Original object: " + obj1);
objOut.writeObject(obj1);
System.out.println("Object serialized to '" + filename + "' successfully.");
} catch (IOException e) {
System.out.println("Error during serialization: " + e.getMessage());
}
// Deserialize the object
try (ObjectInputStream objIn = new ObjectInputStream(new FileInputStream(filename))) {
MyObject obj2 = (MyObject) objIn.readObject(); // Cast is required
System.out.println("Object deserialized from '" + filename + "'.");
System.out.println("Deserialized object: " + obj2);
// Note: password is null because it was transient
} catch (IOException e) {
System.out.println("Error during deserialization (IO): " + e.getMessage());
} catch (ClassNotFoundException e) {
System.out.println("Error during deserialization (Class Not Found): " + e.getMessage());
}
}
}
Code Output:
Original object: Name: Alice, ID: 101, Password: secret123
Object serialized to 'object_data.ser' successfully.
Object deserialized from 'object_data.ser'.
Deserialized object: Name: Alice, ID: 101, Password: null
Notice that the password
field is null
after deserialization because it was marked transient
and thus not saved in the byte stream. This demonstrates a key aspect of controlling what parts of an object’s state are persisted.
Developers may efficiently manage persistent data, create reliable programs, and handle a variety of data transfer requirements by comprehending these core ideas of Java I/O streams, file management, and object serialization.