1. The Hook (The "Byte-Sized" Intro)
- In a Nutshell: Best practices for file I/O: (1) Always use try-with-resources (auto-close), (2) Explicit UTF-8 encoding (StandardCharsets.UTF_8), (3) Buffer everything (BufferedReader/Writer), (4) Prefer NIO.2 (Path/Files over File), (5) Handle exceptions properly, (6) Use platform-independent paths (File.separator), (7) Never trust user input (sanitize paths), (8) Close streams in finally (or try-with-resources).
- Golden rule: Resource leaks = memory leaks = production disaster!
Think of water faucet. Bad practices = leave faucets running (resource leaks), use wrong pipes (wrong encoding), no maintenance (no exception handling). Good practices = turn off faucets (close streams), use right pipes (UTF-8), regular checks (proper exceptions)!
2. Conceptual Clarity (The "Simple" Tier)
π‘ The Analogy
- Try-with-resources: Automatic door closer (closes automatically)
- Buffering: Batch delivery (not one-by-one)
- UTF-8: Universal translator (works everywhere)
3. Technical Mastery (The "Deep Dive")
10 Golden Rules
- Use Try-with-Resources
- Always Specify Encoding
- Buffer I/O Operations
- Prefer NIO.2 over Legacy
- Handle Exceptions Properly
- Close Streams in Reverse Order
- Use Platform-Independent Paths
- Sanitize User Input
- Don't Ignore Return Values
- Test Edge Cases
4. Interactive & Applied Code
java
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.*;
import java.util.*;
public class BestPracticesDemo {
// β
RULE 1: Try-with-resources
static void demonstrateTryWithResources() {
// β OLD WAY: Manual cleanup
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("file.txt"));
String line = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// β
NEW WAY: Automatic cleanup
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
String line = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
}
// β
RULE 2: Explicit encoding
static void demonstrateEncoding() throws IOException {
Path file = Paths.get("data.txt");
// β BAD: Platform-dependent encoding
// FileReader reader = new FileReader(file.toFile());
// β
GOOD: Explicit UTF-8
try (var reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
// Read with UTF-8
}
// β
GOOD: Explicit UTF-8 for writing
try (var writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) {
writer.write("Hello with UTF-8: δΈζ");
}
}
// β
RULE 3: Buffering
static void demonstrateBuffering() throws IOException {
Path file = Paths.get("large.txt");
// β SLOW: No buffering
try (FileReader fr = new FileReader(file.toFile())) {
int ch;
while ((ch = fr.read()) != -1) {
// Each read() is a system call!
}
}
// β
FAST: With buffering
try (BufferedReader br = Files.newBufferedReader(file)) {
String line;
while ((line = br.readLine()) != null) {
// Reads from buffer (batch system calls)
}
}
}
// β
RULE 4: NIO.2 over legacy
static void demonstrateNIO2() throws IOException {
// β LEGACY: File class
File file = new File("test.txt");
if (file.exists()) {
FileReader reader = new FileReader(file);
// Verbose, no encoding control
}
// β
MODERN: NIO.2
Path path = Paths.get("test.txt");
if (Files.exists(path)) {
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
// Clean, explicit encoding, one-liner!
}
}
// β
RULE 5: Proper exception handling
static void demonstrateExceptionHandling() {
Path file = Paths.get("data.txt");
// β BAD: Swallow exceptions
try {
Files.readAllLines(file);
} catch (IOException e) {
// Silent failure!
}
// β
GOOD: Log and handle
try {
List<String> lines = Files.readAllLines(file, StandardCharsets.UTF_8);
} catch (FileNotFoundException e) {
System.err.println("File not found: " + file);
// Specific handling
} catch (IOException e) {
System.err.println("I/O error reading file: " + e.getMessage());
// Log, rethrow, or handle
}
}
// β
RULE 7: Platform-independent paths
static void demonstratePlatformIndependence() {
// β BAD: Hardcoded separator
// String path = "folder\\subfolder\\file.txt"; // Windows only!
// β
GOOD: Platform-independent
Path path = Paths.get("folder", "subfolder", "file.txt");
// Works on Windows, Linux, Mac!
// Or with File.separator
String pathStr = "folder" + File.separator + "subfolder" +
File.separator + "file.txt";
}
// β
RULE 8: Sanitize user input
static void demonstrateInputSanitization(String userInput) throws IOException {
// β DANGEROUS: Path traversal attack
// Path file = Paths.get("uploads/" + userInput);
// User inputs: "../../../etc/passwd" β security breach!
// β
SAFE: Validate and sanitize
Path baseDir = Paths.get("uploads").toAbsolutePath().normalize();
Path file = baseDir.resolve(userInput).normalize();
// Check if file is within allowed directory
if (!file.startsWith(baseDir)) {
throw new SecurityException("Invalid file path");
}
// Now safe to use
Files.readAllBytes(file);
}
// β
RULE 9: Check return values
static void demonstrateReturnValues() throws IOException {
File file = new File("test.txt");
// β BAD: Ignore return value
file.delete(); // What if it fails?
// β
GOOD: Check result
if (!file.delete()) {
System.err.println("Failed to delete file");
}
// β
BETTER: Use NIO.2 (throws exception on failure)
Files.delete(Paths.get("test.txt")); // Throws if fails
}
}
// Performance comparison
class PerformanceComparison {
public static void main(String[] args) throws Exception {
Path file = Paths.get("test.txt");
// Create large file
try (var writer = Files.newBufferedWriter(file)) {
for (int i = 0; i < 100000; i++) {
writer.write("Line " + i + "\n");
}
}
// Unbuffered (SLOW)
long start = System.currentTimeMillis();
try (FileReader fr = new FileReader(file.toFile())) {
while (fr.read() != -1) {}
}
long unbuffered = System.currentTimeMillis() - start;
// Buffered (FAST)
start = System.currentTimeMillis();
try (BufferedReader br = Files.newBufferedReader(file)) {
while (br.readLine() != null) {}
}
long buffered = System.currentTimeMillis() - start;
System.out.println("Unbuffered: " + unbuffered + "ms");
System.out.println("Buffered: " + buffered + "ms");
System.out.println("Speedup: " + (unbuffered / (double) buffered) + "x");
}
}5. The Comparison & Decision Layer
| Task | Don't Use | Use This |
|---|---|---|
| Reading text | FileReader | Files.readAllLines() + UTF-8 |
| Writing text | FileWriter | Files.writeString() + UTF-8 |
| File ops | File class | Path/Files (NIO.2) |
| Resource mgmt | Manual finally | Try-with-resources |
| Paths | Hardcoded "/" or "\" | Paths.get() |
6. The "Interview Corner" (The Edge)
The "Killer" Interview Question: "What's wrong with this code?"
java
public void copyFile(String src, String dest) throws IOException {
FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dest);
int data;
while ((data = fis.read()) != -1) {
fos.write(data);
}
fis.close();
fos.close();
}Answer: Multiple issues:
- If exception during copy β streams never closed (resource leak)
- No buffering (extremely slow)
- Closes in wrong order (should close in reverse)
Fix:
java
public void copyFile(String src, String dest) throws IOException {
// β
Auto-close, buffered, NIO.2
Files.copy(Paths.get(src), Paths.get(dest),
StandardCopyOption.REPLACE_EXISTING);
}Pro-Tips:
- Delete vs deleteIfExists:
java
// Throws if file doesn't exist
Files.delete(path);
// Safe (returns boolean)
boolean deleted = Files.deleteIfExists(path);- Create parent directories:
java
Path file = Paths.get("a/b/c/file.txt");
// β Fails if a/b/c doesn't exist
Files.createFile(file);
// β
Creates parent dirs first
Files.createDirectories(file.getParent());
Files.createFile(file);- Atomic operations:
java
// Write atomically (all-or-nothing)
Files.write(path, bytes, StandardOpenOption.CREATE,
StandardOpenOption.WRITE, StandardOpenOption.SYNC);