File I/O Part 1
Reading and writing files
Introduction to File I/O
Java provides comprehensive APIs for file and I/O operations through two main packages: the traditional java.io package and the modern java.nio (NIO.2) package introduced in Java 7.
Evolution of File I/O in Java
Key Packages Overview
| Package | Key Classes | When to Use | Performance |
|---|---|---|---|
| java.io | File, FileInputStream, BufferedReader | Simple file operations, streams | Good for small files |
| java.nio.file | Path, Files, FileSystem | Modern file operations, large files | Optimized, memory-efficient |
| java.nio.channels | FileChannel, ByteBuffer | High-performance, direct I/O | Fastest for large files |
Quick Comparison: Traditional vs NIO.2
Traditional vs Modern Approach
import java.io.*;
import java.nio.file.*;
public class FileIOComparison {
// Traditional java.io approach
public static void readFileTraditional(String path) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(path));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
}
// Modern NIO.2 approach (Java 7+)
public static void readFileModern(String path) throws IOException {
Files.lines(Paths.get(path))
.forEach(System.out::println);
}
// Java 11+ - simplest approach
public static void readFileSimple(String path) throws IOException {
String content = Files.readString(Path.of(path));
System.out.println(content);
}
}
Traditional I/O (java.io)
The java.io package provides stream-based I/O operations. Understanding these fundamentals is essential even when using modern APIs.
Stream Hierarchy
InputStream / OutputStream (Byte Streams)
|-- FileInputStream / FileOutputStream
|-- BufferedInputStream / BufferedOutputStream
|-- ObjectInputStream / ObjectOutputStream
Reader / Writer (Character Streams)
|-- FileReader / FileWriter
|-- BufferedReader / BufferedWriter
|-- PrintWriter
File Class Operations
File Class Examples
import java.io.*;
public class FileClassDemo {
public static void main(String[] args) {
File file = new File("example.txt");
// File properties
System.out.println("Exists: " + file.exists());
System.out.println("Is File: " + file.isFile());
System.out.println("Is Directory: " + file.isDirectory());
System.out.println("Can Read: " + file.canRead());
System.out.println("Can Write: " + file.canWrite());
// File metadata
System.out.println("Name: " + file.getName());
System.out.println("Path: " + file.getPath());
System.out.println("Absolute Path: " + file.getAbsolutePath());
System.out.println("Size: " + file.length() + " bytes");
// Directory operations
File dir = new File("mydir");
dir.mkdir(); // Create single directory
dir.mkdirs(); // Create directory hierarchy
// List files
File[] files = dir.listFiles();
if (files != null) {
for (File f : files) {
System.out.println(f.getName());
}
}
}
}
BufferedReader and BufferedWriter
Buffered I/O Operations
import java.io.*;
public class BufferedIOExample {
// Reading with BufferedReader (try-with-resources)
public static void readFile(String filename) {
try (BufferedReader br = new BufferedReader(new FileReader(filename))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
// Writing with BufferedWriter
public static void writeFile(String filename, String content) {
try (BufferedWriter bw = new BufferedWriter(new FileWriter(filename))) {
bw.write(content);
bw.newLine(); // Platform-independent line separator
bw.flush(); // Ensure data is written
} catch (IOException e) {
e.printStackTrace();
}
}
// Append to file
public static void appendToFile(String filename, String content) {
try (BufferedWriter bw = new BufferedWriter(
new FileWriter(filename, true))) { // true = append mode
bw.write(content);
} catch (IOException e) {
e.printStackTrace();
}
}
}
New I/O (NIO.2 - java.nio)
Java NIO.2 (introduced in Java 7) provides a more modern, efficient, and feature-rich API for file operations with better exception handling, symbolic link support, and platform-independent file attributes.
Why NIO.2 Over Traditional I/O?
Path vs File - Deep Dive
| Feature | File (java.io) | Path (java.nio) |
|---|---|---|
| Performance | Slower, more system calls | Optimized, fewer system calls |
| Exception Handling | Returns false on error | Throws specific exceptions |
| Symbolic Links | Limited support | Full support with LinkOption |
| File Attributes | Basic (size, modified time) | Comprehensive (owner, permissions, ACLs) |
| File System Support | Default file system only | Multiple file systems (ZIP, JAR, custom) |
| Atomic Operations | No guarantee | Atomic move and copy options |
| Directory Streams | Array of files | Stream-based iteration |
Path API - Advanced Examples
Comprehensive Path Operations
import java.nio.file.*;
import java.io.IOException;
public class PathOperationsAdvanced {
public static void main(String[] args) throws IOException {
// Creating Paths - Multiple ways
Path path1 = Path.of("data", "file.txt"); // Java 11+
Path path2 = Paths.get("data/file.txt"); // Java 7+
Path path3 = Path.of(URI.create("file:///home/user/file.txt"));
// Working with home directory
Path home = Paths.get(System.getProperty("user.home"));
Path config = home.resolve(".config/myapp/settings.json");
// Path decomposition
Path fullPath = Path.of("/home/user/documents/report.pdf");
System.out.println("File name: " + fullPath.getFileName());
System.out.println("Parent: " + fullPath.getParent());
System.out.println("Root: " + fullPath.getRoot());
System.out.println("Name count: " + fullPath.getNameCount());
// Iterating path elements
for (Path element : fullPath) {
System.out.println("Element: " + element);
}
// Path resolution - combining paths
Path base = Path.of("/home/user");
Path resolved = base.resolve("documents/file.txt");
// Resolve sibling - replace file name
Path file = Path.of("/data/old.txt");
Path sibling = file.resolveSibling("new.txt");
// Relativizing - create relative path
Path path = Path.of("/home/user/documents/file.txt");
Path base2 = Path.of("/home/user");
Path relative = base2.relativize(path);
// Normalize - clean up path
Path messy = Path.of("./data/../data/./file.txt");
Path clean = messy.normalize();
// Convert to absolute path
Path relative2 = Path.of("data/file.txt");
Path absolute = relative2.toAbsolutePath();
// Compare paths
boolean isSame = Files.isSameFile(path1, path2);
// StartsWith / Ends With
boolean isInHome = fullPath.startsWith("/home");
boolean isPdf = fullPath.toString().endsWith(".pdf");
}
}
Files Utility Class - Complete Guide
Files Class Advanced Operations
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.io.IOException;
import java.util.*;
public class FilesUtilityComplete {
public static void main(String[] args) throws IOException {
Path path = Path.of("example.txt");
// ===== Existence and Type Checks =====
boolean exists = Files.exists(path);
boolean notExists = Files.notExists(path);
boolean isRegular = Files.isRegularFile(path);
boolean isDir = Files.isDirectory(path);
boolean isSymLink = Files.isSymbolicLink(path);
boolean isHidden = Files.isHidden(path);
// ===== Permission Checks =====
boolean isReadable = Files.isReadable(path);
boolean isWritable = Files.isWritable(path);
boolean isExecutable = Files.isExecutable(path);
// ===== File Metadata =====
long size = Files.size(path);
FileTime lastModified = Files.getLastModifiedTime(path);
UserPrincipal owner = Files.getOwner(path);
// ===== Create Operations =====
Path newFile = Files.createFile(Path.of("new.txt"));
Files.createDirectory(Path.of("newdir"));
Files.createDirectories(Path.of("a/b/c/d"));
Path tempFile = Files.createTempFile("prefix", ".tmp");
Path tempDir = Files.createTempDirectory("myapp");
// ===== Copy Operations =====
Path source = Path.of("source.txt");
Path target = Path.of("target.txt");
Files.copy(source, target);
Files.copy(source, target,
StandardCopyOption.REPLACE_EXISTING,
StandardCopyOption.COPY_ATTRIBUTES,
LinkOption.NOFOLLOW_LINKS);
// ===== Move/Rename Operations =====
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
// ===== Delete Operations =====
Files.delete(path);
Files.deleteIfExists(path);
// ===== File Attributes =====
BasicFileAttributes attrs = Files.readAttributes(
path, BasicFileAttributes.class);
System.out.println("Size: " + attrs.size());
System.out.println("Created: " + attrs.creationTime());
System.out.println("Modified: " + attrs.lastModifiedTime());
// ===== File Comparison =====
long mismatch = Files.mismatch(path, target);
}
}
Working with File Systems
File System Operations
import java.nio.file.*;
import java.net.URI;
public class FileSystemExample {
public static void main(String[] args) throws Exception {
// Get default file system
FileSystem defaultFS = FileSystems.getDefault();
System.out.println("File System: " + defaultFS);
// Get root directories
for (Path root : defaultFS.getRootDirectories()) {
System.out.println("Root: " + root);
FileStore store = Files.getFileStore(root);
System.out.println(" Total: " + store.getTotalSpace() / (1024 * 1024 * 1024) + " GB");
System.out.println(" Available: " + store.getUsableSpace() / (1024 * 1024 * 1024) + " GB");
}
// Working with ZIP file system
Map<String, String> env = new HashMap<>();
env.put("create", "true");
URI uri = URI.create("jar:file:/path/to/file.zip");
try (FileSystem zipFS = FileSystems.newFileSystem(uri, env)) {
Path fileInZip = zipFS.getPath("/data/file.txt");
Files.writeString(fileInZip, "Content in ZIP");
}
}
}
Reading Files Efficiently
Choose the right reading method based on file size, content type, and performance requirements. Modern Java provides multiple approaches, each optimized for different scenarios.
Reading Strategies - Decision Tree
Text File Reading - Complete Examples
Comprehensive Text Reading
import java.nio.file.*;
import java.io.*;
import java.util.*;
import java.util.stream.*;
public class TextFileReading {
// Method 1: Read entire file as String (Java 11+)
public static String readAsString(String filename) throws IOException {
return Files.readString(Path.of(filename), StandardCharsets.UTF_8);
}
// Method 2: Read all lines into List
public static List<String> readAllLines(String filename) throws IOException {
return Files.readAllLines(Path.of(filename), StandardCharsets.UTF_8);
}
// Method 3: Stream-based reading (memory efficient)
public static void processLargeFile(String filename) throws IOException {
try (Stream<String> lines = Files.lines(Path.of(filename))) {
lines.filter(line -> !line.trim().isEmpty())
.filter(line -> line.contains("ERROR"))
.map(String::toUpperCase)
.forEach(System.out::println);
}
}
// Method 4: BufferedReader for custom processing
public static void readWithBufferedReader(String filename) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(
Path.of(filename), StandardCharsets.UTF_8)) {
String line;
int lineNumber = 0;
while ((line = reader.readLine()) != null) {
lineNumber++;
processLine(line, lineNumber);
}
}
}
// Method 5: Scanner for formatted data
public static void readWithScanner(String filename) throws IOException {
try (Scanner scanner = new Scanner(Path.of(filename))) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println(line);
}
scanner.useDelimiter(",");
while (scanner.hasNext()) {
String token = scanner.next();
System.out.println(token.trim());
}
}
}
// Method 6: Read CSV file
public static List<String[]> readCSV(String filename) throws IOException {
try (Stream<String> lines = Files.lines(Path.of(filename))) {
return lines.skip(1)
.map(line -> line.split(","))
.collect(Collectors.toList());
}
}
// Method 7: Read and count words
public static Map<String, Long> wordFrequency(String filename)
throws IOException {
try (Stream<String> lines = Files.lines(Path.of(filename))) {
return lines.flatMap(line -> Arrays.stream(line.split("\\s+")))
.map(String::toLowerCase)
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.counting()));
}
}
}
Binary File Reading - Advanced
Binary File Operations
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
public class BinaryFileReading {
// Method 1: Read all bytes (small files)
public static byte[] readAllBytes(String filename) throws IOException {
return Files.readAllBytes(Path.of(filename));
}
// Method 2: Buffered reading for large files
public static void readLargeBinary(String filename) throws IOException {
try (InputStream is = Files.newInputStream(Path.of(filename));
BufferedInputStream bis = new BufferedInputStream(is)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
processBytes(buffer, bytesRead);
}
}
}
// Method 3: FileChannel for high performance
public static void readWithChannel(String filename) throws IOException {
try (FileChannel channel = FileChannel.open(
Path.of(filename), StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 64);
while (channel.read(buffer) > 0) {
buffer.flip();
while (buffer.hasRemaining()) {
byte b = buffer.get();
}
buffer.clear();
}
}
}
// Method 4: Memory-mapped file (fastest for large files)
public static void readMemoryMapped(String filename) throws IOException {
try (FileChannel channel = FileChannel.open(
Path.of(filename), StandardOpenOption.READ)) {
long size = channel.size();
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_ONLY, 0, size);
while (buffer.hasRemaining()) {
byte b = buffer.get();
}
}
}
// Method 5: Random access file
public static void randomAccess(String filename) throws IOException {
try (RandomAccessFile raf = new RandomAccessFile(filename, "r")) {
raf.seek(100);
int value = raf.readInt();
double d = raf.readDouble();
String line = raf.readLine();
long position = raf.getFilePointer();
raf.seek(raf.length() - 100);
}
}
}
Performance Optimization Tips
Reading Performance Best Practices
- Buffer Size: Use 8KB-64KB buffers for optimal performance
- Direct Buffers: Use ByteBuffer.allocateDirect() for large files
- Memory Mapping: Fastest for files >100MB accessed sequentially
- Stream Processing: Use Files.lines() to avoid loading entire file
- Charset Specification: Always specify UTF-8 explicitly
- Resource Management: Always use try-with-resources
Writing Files Efficiently
Modern Java provides multiple ways to write files, each optimized for different scenarios.
Simple File Writing (Java 11+)
Modern File Writing
import java.nio.file.*;
import java.io.IOException;
public class WriteFileModern {
// Write string to file (Java 11+)
public static void writeString(String filename, String content)
throws IOException {
Files.writeString(Path.of(filename), content);
}
// Write lines
public static void writeLines(String filename, List<String> lines)
throws IOException {
Files.write(Path.of(filename), lines);
}
// Append to file
public static void appendContent(String filename, String content)
throws IOException {
Files.writeString(Path.of(filename), content,
StandardOpenOption.APPEND);
}
// Write with BufferedWriter
public static void writeWithBuffer(String filename) throws IOException {
try (BufferedWriter writer = Files.newBufferedWriter(Path.of(filename))) {
writer.write("Line 1");
writer.newLine();
writer.write("Line 2");
}
}
}
Binary File Writing
Writing Binary Files
import java.nio.file.*;
import java.nio.*;
public class WriteBinaryFile {
// Write bytes directly
public static void writeBytes(String filename, byte[] data)
throws IOException {
Files.write(Path.of(filename), data);
}
// Write using OutputStream
public static void writeWithStream(String filename, byte[] data)
throws IOException {
try (OutputStream os = Files.newOutputStream(Path.of(filename))) {
os.write(data);
os.flush();
}
}
// High-performance with FileChannel
public static void writeWithChannel(String filename, byte[] data)
throws IOException {
try (FileChannel channel = FileChannel.open(
Path.of(filename),
StandardOpenOption.CREATE,
StandardOpenOption.WRITE)) {
ByteBuffer buffer = ByteBuffer.wrap(data);
channel.write(buffer);
}
}
}
Performance Comparison
Files.writeString()
Use Case: Small text files
Performance: Fast
BufferedWriter
Use Case: Large text files
Performance: Efficient
FileChannel
Use Case: Binary files
Performance: Fastest
Serialization and Deserialization
Serialization converts objects into byte streams for storage or transmission. Java provides built-in serialization and modern alternatives.
Java Serialization
Object Serialization
import java.io.*;
class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private transient int age; // Won't be serialized
private static String company; // Static fields not serialized
}
public class SerializationExample {
// Serialize object to file
public static void serialize(Person person, String filename) {
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(filename))) {
oos.writeObject(person);
} catch (IOException e) {
e.printStackTrace();
}
}
// Deserialize object from file
public static Person deserialize(String filename) {
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(filename))) {
return (Person) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}
Modern Alternatives - JSON Serialization
JSON with Jackson/Gson
import com.google.gson.*;
import java.nio.file.*;
public class JsonSerialization {
private static final Gson gson = new GsonBuilder()
.setPrettyPrinting()
.create();
// Serialize to JSON
public static void serializeToJson(Object obj, String filename)
throws IOException {
String json = gson.toJson(obj);
Files.writeString(Path.of(filename), json);
}
// Deserialize from JSON
public static <T> T deserializeFromJson(String filename, Class<T> clazz)
throws IOException {
String json = Files.readString(Path.of(filename));
return gson.fromJson(json, clazz);
}
}
Advanced File Operations
File Watching Service
Monitor File Changes
import java.nio.file.*;
public class FileWatcher {
public static void watchDirectory(String dirPath) throws IOException {
WatchService watchService = FileSystems.getDefault().newWatchService();
Path path = Path.of(dirPath);
path.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);
while (true) {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
System.out.println("Event: " + event.kind() +
" - File: " + event.context());
}
key.reset();
}
}
}
Memory-Mapped Files
High-Performance I/O
import java.nio.*;
import java.nio.channels.*;
public class MemoryMappedFile {
public static void readMemoryMapped(String filename)
throws IOException {
try (RandomAccessFile file = new RandomAccessFile(filename, "r");
FileChannel channel = file.getChannel()) {
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_ONLY, 0, channel.size());
while (buffer.hasRemaining()) {
byte b = buffer.get();
}
}
}
}
Walking File Trees
Traverse Directory Structure
import java.nio.file.*;
public class FileTreeWalker {
public static void walkFileTree(String startPath) throws IOException {
Files.walk(Path.of(startPath))
.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".java"))
.forEach(System.out::println);
}
// Find files by criteria
public static void findLargeFiles(String dir) throws IOException {
Files.find(Path.of(dir),
Integer.MAX_VALUE,
(path, attrs) -> attrs.isRegularFile() &&
attrs.size() > 1_000_000)
.forEach(path -> System.out.println(path + ": " +
Files.size(path) + " bytes"));
}
// Copy directory recursively
public static void copyDirectory(Path source, Path target)
throws IOException {
Files.walk(source)
.forEach(sourcePath -> {
Path targetPath = target.resolve(source.relativize(sourcePath));
try {
Files.copy(sourcePath, targetPath);
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
Best Practices and Performance
Resource Management
Proper Resource Handling
import java.nio.file.*;
public class ResourceManagement {
// CORRECT: Try-with-resources (Java 7+)
public static void correctApproach(String filename) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(Path.of(filename))) {
reader.lines().forEach(System.out::println);
} // Automatically closed
}
// AVOID: Manual closing
public static void manualApproach(String filename) {
BufferedReader reader = null;
try {
reader = Files.newBufferedReader(Path.of(filename));
// Read file
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Performance Optimization
Buffer Size
- Default: 8KB - good for most cases
- Large files: 64KB - 1MB buffers
- Network files: Smaller buffers (4-16KB)
Reading Strategy
- Sequential: Use BufferedReader
- Random access: Use RandomAccessFile
- Large files: Use streams/channels
Writing Strategy
- Batch writes when possible
- Use buffered streams
- Flush strategically
Common Pitfalls and Solutions
| Pitfall | Problem | Solution |
|---|---|---|
| Platform-dependent paths | Hard-coded ”/” or ”\“ | Use File.separator or Path API |
| Not closing resources | Memory leaks | Always use try-with-resources |
| Reading large files at once | OutOfMemoryError | Use streams or BufferedReader |
| Ignoring character encoding | Corrupted text | Specify UTF-8 explicitly |
Performance Comparison
Benchmarking Example
import java.nio.file.*;
public class PerformanceBenchmark {
public static void benchmark(String filename) {
long start, end;
// Method 1: Files.readAllLines()
start = System.nanoTime();
try {
List<String> lines = Files.readAllLines(Path.of(filename));
System.out.println("Lines: " + lines.size());
} catch (IOException e) { }
end = System.nanoTime();
System.out.println("readAllLines: " + (end - start) / 1_000_000 + " ms");
// Method 2: Files.lines() stream
start = System.nanoTime();
try (Stream<String> stream = Files.lines(Path.of(filename))) {
long count = stream.count();
System.out.println("Lines: " + count);
} catch (IOException e) { }
end = System.nanoTime();
System.out.println("lines() stream: " + (end - start) / 1_000_000 + " ms");
}
}
Best Practice Checklist
- Always use try-with-resources for automatic cleanup
- Use NIO.2 (Path/Files) for new code
- Specify character encoding explicitly (UTF-8)
- Use buffered streams for better performance
- Handle exceptions appropriately
- Use Files.lines() for large files
- Validate file paths and permissions
- Close streams in finally or use try-with-resources