NIO.2: Path và Files
Sau bài này, bạn sẽ:
- Hiểu Path interface - replacement hiện đại cho File class (Java 7+)
- Sử dụng Files utility class cho các operations: copy, move, delete, walk directory tree
- Áp dụng Stream API với files: Files.lines(), Files.walk(), Files.find()
- Implement WatchService để monitor file system changes
- So sánh File (legacy) vs Path (modern) và biết khi nào dùng cái gì
Bài trước: NIO: Channels và Buffers — Đã học về Channels, Buffers, và FileChannel. Bài này sẽ tìm hiểu NIO.2 với Path và Files - modern file API từ Java 7.
Java NIO.2 (từ Java 7) giới thiệu API mới cho file I/O: Path và Files. Đây là replacement hiện đại cho java.io.File class.
Path Interface - Thay thế File class
Path đại diện cho một đường dẫn (path) trong file system. Khác với File, Path là interface (không phải class).
Tạo Path object
import java.nio.file.Path;
import java.nio.file.Paths;
public class PathCreation {
public static void main(String[] args) {
// Cách 1: Paths.get() (Java 7+)
Path path1 = Paths.get("data.txt");
Path path2 = Paths.get("/Users/luatnq/documents/file.txt");
Path path3 = Paths.get("data", "config", "app.properties");
// → data/config/app.properties
// Cách 2: Path.of() (Java 11+) - RECOMMENDED
Path path4 = Path.of("data.txt");
Path path5 = Path.of("/Users/luatnq/documents/file.txt");
Path path6 = Path.of("data", "config", "app.properties");
// Cách 3: Từ URI
Path path7 = Paths.get(java.net.URI.create("file:///tmp/data.txt"));
// Cách 4: Từ File object (legacy)
java.io.File file = new java.io.File("data.txt");
Path path8 = file.toPath();
System.out.println("Path created: " + path4);
}
}
Path.of() ngắn gọn hơn Paths.get() và được khuyến nghị từ Java 11 trở đi.
Path Operations - resolve, relativize, normalize
resolve() - Nối paths
import java.nio.file.Path;
public class PathResolve {
public static void main(String[] args) {
Path base = Path.of("/home/user");
Path relative = Path.of("documents/file.txt");
// resolve() - Nối path
Path full = base.resolve(relative);
System.out.println(full);
// Output: /home/user/documents/file.txt
// resolve() với absolute path → trả về absolute path
Path absolute = Path.of("/tmp/data.txt");
Path result = base.resolve(absolute);
System.out.println(result);
// Output: /tmp/data.txt (không nối!)
// Nối String
Path path = Path.of("/data").resolve("file.txt");
System.out.println(path);
// Output: /data/file.txt
}
}
Path.resolve vs relativize Examples
relativize() - Tạo relative path giữa 2 paths
import java.nio.file.Path;
public class PathRelativize {
public static void main(String[] args) {
Path path1 = Path.of("/home/user/documents/file.txt");
Path path2 = Path.of("/home/user/pictures");
// Tạo relative path từ path2 đến path1
Path relative = path2.relativize(path1);
System.out.println(relative);
// Output: ../documents/file.txt
// Verify
Path resolved = path2.resolve(relative);
System.out.println(resolved);
// Output: /home/user/documents/file.txt
}
}
####normalize() - Loại bỏ redundant elements
import java.nio.file.Path;
public class PathNormalize {
public static void main(String[] args) {
// Path với . và ..
Path messy = Path.of("/home/user/../luatnq/./documents/../data/file.txt");
System.out.println("Original: " + messy);
// Output: /home/user/../luatnq/./documents/../data/file.txt
// normalize() - Loại bỏ . và ..
Path clean = messy.normalize();
System.out.println("Normalized: " + clean);
// Output: /home/luatnq/data/file.txt
// Ví dụ khác
Path path = Path.of("foo/bar/../baz/./file.txt");
System.out.println(path.normalize());
// Output: foo/baz/file.txt
}
}
import java.nio.file.Path;
public class PathResolveTrap {
public static void main(String[] args) {
Path base = Path.of("/home/user");
// Resolve with empty string
Path result = base.resolve("");
System.out.println(result);
// Output: /home/user
// ❌ NOT "/home/user/" - just returns the same path!
// Resolve with "." also returns same
Path result2 = base.resolve(".");
System.out.println(result2);
// Output: /home/user/. (not normalized)
// Normalized:
System.out.println(result2.normalize());
// Output: /home/user
}
}
Path Properties
import java.nio.file.Path;
public class PathProperties {
public static void main(String[] args) {
Path path = Path.of("/home/user/documents/report.pdf");
// Lấy tên file
System.out.println("File name: " + path.getFileName());
// Output: report.pdf
// Lấy parent directory
System.out.println("Parent: " + path.getParent());
// Output: /home/user/documents
// Lấy root
System.out.println("Root: " + path.getRoot());
// Output: /
// Số lượng elements
System.out.println("Name count: " + path.getNameCount());
// Output: 4 (home, user, documents, report.pdf)
// Lấy element theo index
System.out.println("Element [0]: " + path.getName(0));
// Output: home
// Subpath
System.out.println("Subpath(1, 3): " + path.subpath(1, 3));
// Output: user/documents
// Absolute vs relative
System.out.println("Is absolute? " + path.isAbsolute());
// Output: true
// Convert to absolute
Path relative = Path.of("data.txt");
System.out.println("Absolute: " + relative.toAbsolutePath());
// Output: /current/working/directory/data.txt
// Convert to real path (resolves symlinks)
try {
Path real = path.toRealPath();
System.out.println("Real path: " + real);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Files Utility Class - Đọc/ghi file đơn giản
Files class cung cấp static utility methods cho file operations - đơn giản hơn nhiều so với java.io!
Files.readAllLines() - Đọc toàn bộ file vào List
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class FilesReadAllLines {
public static void main(String[] args) {
Path path = Path.of("data.txt");
try {
// Đọc toàn bộ file vào List<String>
List<String> lines = Files.readAllLines(path);
System.out.println("Total lines: " + lines.size());
for (int i = 0; i < lines.size(); i++) {
System.out.println((i + 1) + ": " + lines.get(i));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Files.readAllLines vs Files.lines Comparison
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;
public class ReadAllLinesDanger {
public static void main(String[] args) {
Path largePath = Path.of("large.txt"); // 500MB file!
// ❌ BAD: readAllLines() loads ENTIRE file into memory!
try {
List<String> lines = Files.readAllLines(largePath);
// OutOfMemoryError: Java heap space!
System.out.println("Lines: " + lines.size());
} catch (OutOfMemoryError e) {
System.err.println("CRASH! Out of memory: " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
// ✅ GOOD: Files.lines() returns lazy Stream!
try (Stream<String> lines = Files.lines(largePath)) {
long count = lines.count();
// Only reads chunks at a time, NOT entire file!
System.out.println("Lines: " + count);
} catch (Exception e) {
e.printStackTrace();
}
// ✅ GOOD: Process line-by-line
try (Stream<String> lines = Files.lines(largePath)) {
lines.filter(line -> line.contains("ERROR"))
.limit(10) // Stop after 10 matches
.forEach(System.out::println);
// Memory efficient!
} catch (Exception e) {
e.printStackTrace();
}
}
}
Kết luận:
Files.readAllLines()→ Loads ENTIRE file into memory → OutOfMemoryError for files > heap size!Files.lines()→ Returns lazy Stream → Only reads chunks as needed → Memory efficient!
Rule of thumb:
- File < 10MB →
readAllLines()OK - File > 10MB → Use
Files.lines()(Stream API)
Files.readString() - Đọc toàn bộ file thành String (Java 11+)
import java.nio.file.Files;
import java.nio.file.Path;
public class FilesReadString {
public static void main(String[] args) {
Path path = Path.of("message.txt");
try {
// Đọc toàn bộ file thành 1 String
String content = Files.readString(path);
System.out.println(content);
// Với encoding
String contentUtf8 = Files.readString(path,
java.nio.charset.StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Files.readAllBytes() - Đọc binary file
import java.nio.file.Files;
import java.nio.file.Path;
public class FilesReadAllBytes {
public static void main(String[] args) {
Path path = Path.of("image.jpg");
try {
byte[] bytes = Files.readAllBytes(path);
System.out.println("File size: " + bytes.length + " bytes");
// Xử lý binary data...
} catch (Exception e) {
e.printStackTrace();
}
}
}
Files.write() - Ghi file đơn giản
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.List;
public class FilesWrite {
public static void main(String[] args) {
Path path = Path.of("output.txt");
try {
// Ghi List<String>
List<String> lines = Arrays.asList(
"Line 1",
"Line 2",
"Line 3"
);
Files.write(path, lines);
// Ghi String (Java 11+)
Files.writeString(path, "Hello, World!");
// Ghi byte[]
byte[] data = {65, 66, 67}; // 'A', 'B', 'C'
Files.write(path, data);
// Append mode
Files.write(path, lines, StandardOpenOption.APPEND);
System.out.println("File written successfully!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
Files.lines() - Stream API cho file lớn (Java 8+)
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
public class FilesLines {
public static void main(String[] args) {
Path path = Path.of("large.txt");
try {
// Stream<String> - KHÔNG load toàn bộ file vào memory!
try (Stream<String> lines = Files.lines(path)) {
// Đếm số dòng
long count = lines.count();
System.out.println("Total lines: " + count);
}
// Filter và process
try (Stream<String> lines = Files.lines(path)) {
lines.filter(line -> line.contains("ERROR"))
.forEach(System.out::println);
}
// Tìm từ dài nhất
try (Stream<String> lines = Files.lines(path)) {
lines.flatMap(line -> Arrays.stream(line.split("\\s+")))
.max((w1, w2) -> Integer.compare(w1.length(), w2.length()))
.ifPresent(word -> System.out.println("Longest word: " + word));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Files.lines() trả về lazy Stream - chỉ đọc từng dòng khi cần. Perfect cho file gigabyte!
Files.walk() và Files.find() - Duyệt Directory Tree
- Files.walk(): Returns ALL paths in directory tree (no filtering)
- Files.find(): Returns FILTERED paths based on BiPredicate
// walk() - get all, then filter manually
Files.walk(start)
.filter(path -> path.toString().endsWith(".java"))
.forEach(System.out::println);
// find() - filter during traversal (more efficient)
Files.find(start, 10,
(path, attrs) -> path.toString().endsWith(".java"))
.forEach(System.out::println);
When to use:
walk()→ Need all files, or simple filtersfind()→ Complex filters using attributes (size, modified time, etc.)
Files.walk() - Duyệt toàn bộ directory tree
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
public class FilesWalk {
public static void main(String[] args) {
Path start = Path.of(".");
try {
// walk() - Duyệt toàn bộ subdirectories
try (Stream<Path> paths = Files.walk(start)) {
paths.filter(Files::isRegularFile)
.filter(path -> path.toString().endsWith(".java"))
.forEach(System.out::println);
}
// Giới hạn depth (số cấp directory)
try (Stream<Path> paths = Files.walk(start, 2)) { // Max depth = 2
paths.forEach(System.out::println);
}
// Tính tổng size của tất cả files
try (Stream<Path> paths = Files.walk(start)) {
long totalSize = paths
.filter(Files::isRegularFile)
.mapToLong(path -> {
try {
return Files.size(path);
} catch (Exception e) {
return 0;
}
})
.sum();
System.out.println("Total size: " + totalSize + " bytes");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Files.find() - Tìm kiếm files với điều kiện
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.stream.Stream;
public class FilesFind {
public static void main(String[] args) {
Path start = Path.of(".");
try {
// Tìm tất cả .txt files lớn hơn 1KB
try (Stream<Path> paths = Files.find(start, 10,
(path, attrs) -> path.toString().endsWith(".txt")
&& attrs.size() > 1024)) {
paths.forEach(System.out::println);
}
// Tìm files modified trong 24h qua
long oneDayAgo = System.currentTimeMillis() - 24 * 60 * 60 * 1000;
try (Stream<Path> paths = Files.find(start, 5,
(path, attrs) -> attrs.isRegularFile()
&& attrs.lastModifiedTime().toMillis() > oneDayAgo)) {
System.out.println("Recently modified files:");
paths.forEach(System.out::println);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Files.copy(), Files.move(), Files.delete()
Files.copy() - Copy files và directories với options
// StandardCopyOption enum values:
REPLACE_EXISTING // Overwrite destination if exists
COPY_ATTRIBUTES // Copy file attributes (timestamp, permissions, etc.)
ATOMIC_MOVE // Only for Files.move(), not copy
// LinkOption enum values:
NOFOLLOW_LINKS // Don't follow symbolic links
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
public class FilesCopyOptions {
public static void main(String[] args) {
Path source = Path.of("source.txt");
Path dest = Path.of("dest.txt");
try {
// ===== Option 1: Basic copy (fails if dest exists) =====
try {
Files.copy(source, dest);
System.out.println("Copy successful!");
} catch (FileAlreadyExistsException e) {
System.err.println("Destination already exists!");
}
// ===== Option 2: REPLACE_EXISTING =====
Files.copy(source, dest, StandardCopyOption.REPLACE_EXISTING);
System.out.println("Copy with replace!");
// ===== Option 3: COPY_ATTRIBUTES =====
Files.copy(source, dest,
StandardCopyOption.REPLACE_EXISTING,
StandardCopyOption.COPY_ATTRIBUTES);
System.out.println("Copy with attributes!");
// Verify attributes copied
BasicFileAttributes sourceAttrs = Files.readAttributes(
source, BasicFileAttributes.class);
BasicFileAttributes destAttrs = Files.readAttributes(
dest, BasicFileAttributes.class);
System.out.println("Source modified: " +
sourceAttrs.lastModifiedTime());
System.out.println("Dest modified: " +
destAttrs.lastModifiedTime());
// Same timestamp!
// ===== Option 4: NOFOLLOW_LINKS (for symbolic links) =====
Path symlink = Path.of("symlink.txt");
Files.createSymbolicLink(symlink, source);
// Copy symlink itself (not target)
Files.copy(symlink, Path.of("copy_of_symlink.txt"),
StandardCopyOption.REPLACE_EXISTING,
LinkOption.NOFOLLOW_LINKS);
// ===== Copy directory (only directory, not contents!) =====
Path dirSource = Path.of("source_dir");
Path dirDest = Path.of("dest_dir");
Files.copy(dirSource, dirDest);
// ⚠️ Only creates dest_dir, does NOT copy files inside!
// ===== Copy directory recursively =====
copyDirectoryRecursive(dirSource, Path.of("dest_dir_full"));
} catch (Exception e) {
e.printStackTrace();
}
}
static void copyDirectoryRecursive(Path source, Path dest) throws Exception {
Files.walk(source).forEach(sourcePath -> {
try {
Path destPath = dest.resolve(source.relativize(sourcePath));
if (Files.isDirectory(sourcePath)) {
Files.createDirectories(destPath);
} else {
Files.copy(sourcePath, destPath,
StandardCopyOption.REPLACE_EXISTING,
StandardCopyOption.COPY_ATTRIBUTES);
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
Files.move() - Di chuyển/đổi tên files
import java.nio.file.*;
public class FilesMoveTrap {
public static void main(String[] args) {
// ===== TRAP: Move across filesystems = copy + delete =====
Path source = Path.of("/home/user/file.txt"); // Filesystem 1
Path dest = Path.of("/mnt/external/file.txt"); // Filesystem 2 (different!)
try {
// ❌ NOT atomic across filesystems!
Files.move(source, dest, StandardCopyOption.ATOMIC_MOVE);
// AtomicMoveNotSupportedException!
} catch (AtomicMoveNotSupportedException e) {
System.err.println("❌ Cannot atomic move across filesystems!");
// Fallback: Non-atomic move (copy + delete)
try {
Files.move(source, dest, StandardCopyOption.REPLACE_EXISTING);
// This works, but:
// 1. Copies file to dest
// 2. Deletes source
// → If crash between step 1 and 2 → duplicate file!
System.out.println("✅ Non-atomic move succeeded");
} catch (Exception e2) {
e2.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
// ===== ATOMIC_MOVE only works within same filesystem =====
Path source2 = Path.of("/home/user/file1.txt");
Path dest2 = Path.of("/home/user/subdir/file2.txt");
// Same filesystem!
try {
Files.move(source2, dest2, StandardCopyOption.ATOMIC_MOVE);
System.out.println("✅ Atomic move succeeded (same filesystem)");
// Guaranteed: file appears at dest instantly, no intermediate state!
} catch (Exception e) {
e.printStackTrace();
}
}
}
Kết luận:
- Same filesystem:
ATOMIC_MOVEworks → instant rename, guaranteed atomic - Different filesystems:
ATOMIC_MOVEthrows exception → must use copy + delete (NOT atomic!)
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
public class FilesMove {
public static void main(String[] args) {
Path source = Path.of("old_name.txt");
Path dest = Path.of("new_name.txt");
try {
// Move/rename file
Files.move(source, dest);
// Overwrite nếu đã tồn tại
Files.move(source, dest, StandardCopyOption.REPLACE_EXISTING);
// Atomic move (all-or-nothing) - only works within same filesystem!
Files.move(source, dest, StandardCopyOption.ATOMIC_MOVE);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Files.delete() và Files.deleteIfExists()
import java.nio.file.*;
public class FilesDeleteTraps {
public static void main(String[] args) {
Path nonexistent = Path.of("nonexistent.txt");
// ===== TRAP 1: delete() throws NoSuchFileException =====
try {
Files.delete(nonexistent);
System.out.println("Deleted!");
} catch (NoSuchFileException e) {
System.err.println("❌ File not found: " + e.getFile());
// Output: ❌ File not found: nonexistent.txt
} catch (Exception e) {
e.printStackTrace();
}
// ===== SOLUTION: Use deleteIfExists() =====
try {
boolean deleted = Files.deleteIfExists(nonexistent);
if (deleted) {
System.out.println("✅ Deleted!");
} else {
System.out.println("✅ File didn't exist, no error!");
}
// Output: ✅ File didn't exist, no error!
} catch (Exception e) {
e.printStackTrace();
}
// ===== TRAP 2: Directory must be empty! =====
try {
Path dir = Path.of("nonempty_dir");
Files.createDirectories(dir);
Files.createFile(dir.resolve("file.txt"));
Files.delete(dir);
// ❌ DirectoryNotEmptyException!
} catch (DirectoryNotEmptyException e) {
System.err.println("❌ Directory not empty: " + e.getFile());
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.nio.file.Files;
import java.nio.file.Path;
public class FilesDelete {
public static void main(String[] args) {
Path path = Path.of("temp.txt");
try {
// delete() - Ném exception nếu file không tồn tại
Files.delete(path);
// deleteIfExists() - Không ném exception, returns boolean
boolean deleted = Files.deleteIfExists(path);
System.out.println("Deleted: " + deleted);
// Xóa directory (phải rỗng!)
Path dir = Path.of("temp_dir");
Files.delete(dir);
// Xóa directory recursively
deleteDirectoryRecursive(Path.of("some_dir"));
} catch (Exception e) {
e.printStackTrace();
}
}
static void deleteDirectoryRecursive(Path path) throws Exception {
if (Files.isDirectory(path)) {
try (var paths = Files.walk(path)) {
paths.sorted((p1, p2) -> -p1.compareTo(p2)) // Reverse order
.forEach(p -> {
try {
Files.delete(p);
} catch (Exception e) {
e.printStackTrace();
}
});
}
} else {
Files.delete(path);
}
}
}
WatchService - Theo dõi thay đổi File System
WatchService cho phép monitor file system events: file created, modified, deleted.
WatchService Event Flow
Basic WatchService Example
import java.nio.file.*;
public class WatchServiceExample {
public static void main(String[] args) {
Path dir = Path.of("watched_dir");
try {
// Tạo WatchService
WatchService watchService = FileSystems.getDefault().newWatchService();
// Register directory để watch
dir.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);
System.out.println("Watching directory: " + dir);
// Poll for events
while (true) {
WatchKey key = watchService.take(); // Blocking call
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == StandardWatchEventKinds.OVERFLOW) {
continue;
}
@SuppressWarnings("unchecked")
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path filename = ev.context();
System.out.println(kind.name() + ": " + filename);
}
// Reset key
boolean valid = key.reset();
if (!valid) {
break; // Directory no longer accessible
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Use Case: Auto-reload configuration
import java.nio.file.*;
import java.util.Properties;
public class ConfigWatcher {
private static Properties config = new Properties();
public static void main(String[] args) throws Exception {
Path configFile = Path.of("app.properties");
Path configDir = configFile.getParent();
// Load initial config
loadConfig(configFile);
// Watch for changes
WatchService watchService = FileSystems.getDefault().newWatchService();
configDir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
System.out.println("Watching config file for changes...");
while (true) {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
@SuppressWarnings("unchecked")
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path changed = ev.context();
if (changed.equals(configFile.getFileName())) {
System.out.println("Config file changed! Reloading...");
loadConfig(configFile);
System.out.println("Config reloaded.");
}
}
key.reset();
}
}
static void loadConfig(Path path) throws Exception {
config.clear();
config.load(Files.newInputStream(path));
System.out.println("Config loaded: " + config);
}
}
So sánh File vs Path/Files
| Tiêu chí | java.io.File | java.nio.file.Path + Files |
|---|---|---|
| Kiến trúc | Class cụ thể | Interface + utility class |
| API design | Methods trộn lẫn | Tách rời: Path (metadata) + Files (operations) |
| Exception handling | Return false on error | Ném exceptions cụ thể |
| Symbolic links | Không hỗ trợ tốt | Hỗ trợ đầy đủ |
| File attributes | Hạn chế (size, lastModified) | Đầy đủ (permissions, owner, ACL, etc.) |
| Copy/Move | Phải tự implement | Built-in: Files.copy(), Files.move() |
| Directory traversal | listFiles() (array) | Files.walk() (Stream) |
| Watch service | Không có | WatchService |
| Atomic operations | Không có | Files.move(ATOMIC_MOVE) |
| Read/Write convenience | Không có | Files.readAllLines(), writeString(), etc. |
Code comparison: File vs Path/Files
import java.io.*;
import java.nio.file.*;
import java.util.List;
public class FileVsPath {
public static void main(String[] args) throws Exception {
// ===== KIỂM TRA FILE TỒN TẠI =====
// java.io.File
File file1 = new File("data.txt");
if (file1.exists()) {
System.out.println("File exists (File)");
}
// java.nio.file.Path
Path path1 = Path.of("data.txt");
if (Files.exists(path1)) {
System.out.println("File exists (Path)");
}
// ===== ĐỌC TOÀN BỘ FILE =====
// java.io.File - phức tạp!
File file2 = new File("data.txt");
BufferedReader reader = new BufferedReader(new FileReader(file2));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
// java.nio.file.Path - đơn giản!
Path path2 = Path.of("data.txt");
List<String> lines = Files.readAllLines(path2);
lines.forEach(System.out::println);
// ===== COPY FILE =====
// java.io.File - phải tự implement!
File src = new File("source.txt");
File dest = new File("dest.txt");
// ... code phức tạp với streams
// java.nio.file.Path - 1 dòng!
Files.copy(Path.of("source.txt"), Path.of("dest.txt"));
// ===== XỬ LÝ LỖI =====
// java.io.File - không rõ ràng
File file3 = new File("nonexistent.txt");
if (!file3.delete()) {
System.err.println("Delete failed - why?"); // Không biết lý do!
}
// java.nio.file.Path - exception rõ ràng
try {
Files.delete(Path.of("nonexistent.txt"));
} catch (NoSuchFileException e) {
System.err.println("File not found: " + e.getFile());
} catch (AccessDeniedException e) {
System.err.println("Access denied: " + e.getFile());
}
}
}
Luôn dùng Path/Files cho code mới. Chỉ dùng File khi maintain legacy code hoặc làm việc với APIs cũ.
Path Operations Mermaid Diagram
Tổng kết
Path Interface
- Modern replacement cho java.io.File
- Operations: resolve, relativize, normalize
- Properties: getFileName, getParent, toAbsolutePath
- Java 11+: Dùng
Path.of()thay vìPaths.get()
Files Utility Class
- Đọc file: readAllLines, readString, readAllBytes, lines (Stream)
- Ghi file: write, writeString
- Copy/Move/Delete: copy, move, delete, deleteIfExists
- Directory traversal: walk, find (Stream-based)
- Metadata: size, exists, isDirectory, getLastModifiedTime
WatchService
- Monitor file system events
- Use cases: auto-reload config, live file sync, development tools
File vs Path/Files
- Path/Files: Modern, clear exceptions, rich API
- File: Legacy, limited functionality
- Migrate từ File sang Path khi có thể!
Bài tập thực hành:
-
Viết file explorer utility:
- List files trong directory
- Show size, last modified date
- Support recursive listing
- Filter theo extension
-
Implement backup tool:
- Copy directory recursively
- Preserve file attributes
- Support incremental backup (chỉ copy files mới/thay đổi)
- Log các files đã copy
-
Create log analyzer:
- Dùng Files.lines() đọc file log lớn
- Filter theo ERROR level
- Count errors per hour
- Ghi report vào file mới