Chuyển tới nội dung chính

NIO.2: Path và Files

Mục tiêu bài học

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: PathFiles. Đâ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, Pathinterface (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);
}
}
Java 11+: Dùng Path.of()

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
}
}
🔥 Bẫy OCP: Path.resolve("") returns the same path!
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

🔥 Bẫy OCP: Files.readAllLines() → OutOfMemoryError on large files!
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() cho file lớn

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() vs Files.find()
  • 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 filters
  • find() → 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

📖 Files.copy() CopyOptions
// 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

🔥 Bẫy OCP: Files.move() across filesystems = copy + delete (NOT atomic!)
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_MOVE works → instant rename, guaranteed atomic
  • Different filesystems: ATOMIC_MOVE throws 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()

🔥 Bẫy OCP: Files.delete() throws if not exists, deleteIfExists() returns false
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.Filejava.nio.file.Path + Files
Kiến trúcClass cụ thểInterface + utility class
API designMethods trộn lẫnTách rời: Path (metadata) + Files (operations)
Exception handlingReturn false on errorNém exceptions cụ thể
Symbolic linksKhông hỗ trợ tốtHỗ trợ đầy đủ
File attributesHạn chế (size, lastModified)Đầy đủ (permissions, owner, ACL, etc.)
Copy/MovePhải tự implementBuilt-in: Files.copy(), Files.move()
Directory traversallistFiles() (array)Files.walk() (Stream)
Watch serviceKhông cóWatchService
Atomic operationsKhông cóFiles.move(ATOMIC_MOVE)
Read/Write convenienceKhô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());
}
}
}
Recommendation

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:

  1. Viết file explorer utility:

    • List files trong directory
    • Show size, last modified date
    • Support recursive listing
    • Filter theo extension
  2. Implement backup tool:

    • Copy directory recursively
    • Preserve file attributes
    • Support incremental backup (chỉ copy files mới/thay đổi)
    • Log các files đã copy
  3. 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

Đọc thêm