一、Files工具类概述
1.1 Files工具类简介
Files类是Java NIO.2 API中提供的一个实用工具类,位于java.nio.file包中。它包含大量静态方法,用于对文件系统中的文件进行各种操作,如创建、删除、复制、移动、读取和写入文件等。
Files工具类的主要特点包括:
完全由静态方法组成,无需实例化
提供了丰富的文件操作方法
支持原子操作和文件属性操作
与Path接口紧密结合使用
提供了更现代的替代方案来替代传统的File类
1.2 Files与传统File类的对比
下表展示了Files类与传统File类的主要区别:
特性 | Files类 | File类 |
---|---|---|
所属包 | java.nio.file | java.io |
主要配合类型 | Path接口 | 自身就是类 |
方法类型 | 静态方法 | 实例方法 |
异常处理 | 抛出IOException | 返回boolean表示成功/失败 |
符号链接处理 | 明确支持,可配置 | 有限支持 |
原子操作 | 支持原子操作 | 不支持 |
文件属性操作 | 提供丰富API | 有限支持 |
性能 | 通常更高 | 相对较低 |
功能丰富度 | 更丰富的功能集 | 功能相对有限 |
1.3 Files类的核心优势
Files类相比传统File类具有多方面优势:
更丰富的功能:提供了更多高级文件操作,如文件属性访问、目录流处理等
更好的异常处理:通过抛出异常而非返回布尔值,提供更详细的错误信息
原子操作支持:某些操作如移动文件可以保证原子性
符号链接处理:明确支持符号链接及其配置
性能优化:底层实现通常比传统IO更高效
与现代API集成:与NIO.2的其他组件如Path、FileSystem等无缝集成
二、Files工具类基础操作
2.1 文件检查操作
Files类提供了一系列方法用于检查文件状态:
2.1.1 检查文件是否存在
Path path = Paths.get("example.txt");
boolean exists = Files.exists(path);
System.out.println("文件是否存在: " + exists);
方法说明:
exists(Path path, LinkOption... options)
:检查文件是否存在
参数:
path
:要检查的文件路径
options
:可选参数,用于指定如何处理符号链接,常用LinkOption.NOFOLLOW_LINKS
表示不跟随符号链接
返回值:boolean,表示文件是否存在
2.1.2 检查文件是否可读/可写/可执行
Path path = Paths.get("example.txt");
boolean readable = Files.isReadable(path);
boolean writable = Files.isWritable(path);
boolean executable = Files.isExecutable(path);
System.out.println("可读: " + readable);
System.out.println("可写: " + writable);
System.out.println("可执行: " + executable);
方法说明:
isReadable(Path path)
:检查文件是否可读
isWritable(Path path)
:检查文件是否可写
isExecutable(Path path)
:检查文件是否可执行
这些方法不会抛出异常,而是返回false如果检查失败
2.1.3 检查文件类型
Path filePath = Paths.get("example.txt");
Path dirPath = Paths.get("docs");
boolean isFile = Files.isRegularFile(filePath);
boolean isDir = Files.isDirectory(dirPath);
boolean isLink = Files.isSymbolicLink(filePath);
System.out.println("是普通文件: " + isFile);
System.out.println("是目录: " + isDir);
System.out.println("是符号链接: " + isLink);
方法说明:
isRegularFile(Path path, LinkOption... options)
:检查是否是普通文件
isDirectory(Path path, LinkOption... options)
:检查是否是目录
isSymbolicLink(Path path)
:检查是否是符号链接
2.2 文件创建与删除
2.2.1 创建文件
Path newFile = Paths.get("newfile.txt");
try {
// 创建文件,如果文件已存在会抛出FileAlreadyExistsException
Path createdFile = Files.createFile(newFile);
System.out.println("文件创建成功: " + createdFile);
} catch (FileAlreadyExistsException e) {
System.out.println("文件已存在: " + newFile);
} catch (IOException e) {
System.err.println("创建文件失败: " + e.getMessage());
}
方法说明:
createFile(Path path, FileAttribute<?>... attrs)
:创建新文件
参数:
path
:要创建的文件路径
attrs
:可选的文件属性,用于设置创建时的属性
如果文件已存在,抛出FileAlreadyExistsException
2.2.2 创建临时文件
try {
// 在默认临时目录创建临时文件
Path tempFile1 = Files.createTempFile("prefix", ".suffix");
System.out.println("临时文件1: " + tempFile1);
// 在指定目录创建临时文件
Path tempDir = Paths.get("mytemp");
Path tempFile2 = Files.createTempFile(tempDir, "app", ".tmp");
System.out.println("临时文件2: " + tempFile2);
} catch (IOException e) {
e.printStackTrace();
}
方法说明:
createTempFile(String prefix, String suffix, FileAttribute<?>... attrs)
:在默认临时目录创建临时文件
createTempFile(Path dir, String prefix, String suffix, FileAttribute<?>... attrs)
:在指定目录创建临时文件
临时文件名将自动生成,保证唯一性
2.2.3 创建目录
Path newDir = Paths.get("new_directory");
try {
// 创建单个目录
Path createdDir = Files.createDirectory(newDir);
System.out.println("目录创建成功: " + createdDir);
// 创建多级目录
Path multiLevelDir = Paths.get("parent/child/grandchild");
Path createdMultiDir = Files.createDirectories(multiLevelDir);
System.out.println("多级目录创建成功: " + createdMultiDir);
} catch (FileAlreadyExistsException e) {
System.out.println("目录已存在: " + newDir);
} catch (IOException e) {
e.printStackTrace();
}
方法说明:
createDirectory(Path dir, FileAttribute<?>... attrs)
:创建单个目录
createDirectories(Path dir, FileAttribute<?>... attrs)
:创建多级目录(包括所有不存在的父目录)
2.2.4 删除文件或目录
Path fileToDelete = Paths.get("file_to_delete.txt");
Path dirToDelete = Paths.get("dir_to_delete");
try {
// 删除文件
Files.delete(fileToDelete);
System.out.println("文件删除成功");
// 删除目录(必须为空)
Files.delete(dirToDelete);
System.out.println("目录删除成功");
} catch (NoSuchFileException e) {
System.out.println("文件/目录不存在: " + e.getFile());
} catch (DirectoryNotEmptyException e) {
System.out.println("目录不为空,无法删除");
} catch (IOException e) {
e.printStackTrace();
}
// 安全删除,文件不存在不会抛出异常
boolean deleted = Files.deleteIfExists(fileToDelete);
System.out.println("文件是否被删除: " + deleted);
方法说明:
delete(Path path)
:删除文件或空目录,如果不存在抛出NoSuchFileException
deleteIfExists(Path path)
:安全删除,文件不存在返回false而不抛出异常
2.3 文件复制与移动
2.3.1 复制文件
Path source = Paths.get("source.txt");
Path target = Paths.get("target.txt");
try {
// 基本复制
Files.copy(source, target);
System.out.println("文件复制成功");
// 带选项的复制
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
System.out.println("文件复制成功(覆盖已存在文件)");
} catch (FileAlreadyExistsException e) {
System.out.println("目标文件已存在,未指定REPLACE_EXISTING选项");
} catch (IOException e) {
e.printStackTrace();
}
方法说明:
copy(Path source, Path target, CopyOption... options)
:复制文件
常用选项:
StandardCopyOption.REPLACE_EXISTING
:覆盖已存在文件
StandardCopyOption.COPY_ATTRIBUTES
:复制文件属性
LinkOption.NOFOLLOW_LINKS
:不跟随符号链接
2.3.2 移动/重命名文件
Path source = Paths.get("oldname.txt");
Path target = Paths.get("newname.txt");
try {
// 基本移动/重命名
Files.move(source, target);
System.out.println("文件移动/重命名成功");
// 带选项的移动
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
System.out.println("文件移动成功(覆盖已存在文件)");
// 原子移动
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
System.out.println("文件原子移动成功");
} catch (IOException e) {
e.printStackTrace();
}
方法说明:
move(Path source, Path target, CopyOption... options)
:移动或重命名文件
常用选项:
StandardCopyOption.REPLACE_EXISTING
:覆盖已存在文件
StandardCopyOption.ATOMIC_MOVE
:保证移动操作的原子性
2.3.3 复制目录
Files.copy()方法也可以用于复制目录,但需要注意:
只复制空目录,不复制目录内容
要复制目录及其内容需要递归操作
public static void copyDirectory(Path sourceDir, Path targetDir) throws IOException {
// 首先创建目标目录
Files.createDirectories(targetDir);
// 遍历源目录
try (DirectoryStream<Path> stream = Files.newDirectoryStream(sourceDir)) {
for (Path source : stream) {
Path target = targetDir.resolve(source.getFileName());
if (Files.isDirectory(source)) {
// 如果是目录,递归复制
copyDirectory(source, target);
} else {
// 如果是文件,直接复制
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
}
}
}
}
三、文件内容读写操作
3.1 小文件读写
3.1.1 读取所有字节/行
Path filePath = Paths.get("example.txt");
// 读取所有字节
try {
byte[] allBytes = Files.readAllBytes(filePath);
String content = new String(allBytes, StandardCharsets.UTF_8);
System.out.println("文件内容:
" + content);
} catch (IOException e) {
e.printStackTrace();
}
// 读取所有行
try {
List<String> lines = Files.readAllLines(filePath, StandardCharsets.UTF_8);
System.out.println("文件行数: " + lines.size());
for (String line : lines) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
方法说明:
readAllBytes(Path path)
:读取文件所有字节,返回byte[]
readAllLines(Path path, Charset cs)
:读取文件所有行,返回List
注意:这些方法适合小文件,大文件可能导致内存不足
3.1.2 写入字节/行
Path filePath = Paths.get("output.txt");
// 写入字节
try {
String content = "Hello, Files工具类!
第二行内容";
Files.write(filePath, content.getBytes(StandardCharsets.UTF_8));
System.out.println("字节写入成功");
} catch (IOException e) {
e.printStackTrace();
}
// 写入行
try {
List<String> lines = Arrays.asList("第一行", "第二行", "第三行");
Files.write(filePath, lines, StandardCharsets.UTF_8);
System.out.println("行写入成功");
// 追加模式
Files.write(filePath, lines, StandardCharsets.UTF_8,
StandardOpenOption.APPEND);
System.out.println("内容追加成功");
} catch (IOException e) {
e.printStackTrace();
}
方法说明:
write(Path path, byte[] bytes, OpenOption... options)
:写入字节
write(Path path, Iterable<? extends CharSequence> lines, Charset cs, OpenOption... options)
:写入行
常用OpenOption:
StandardOpenOption.CREATE
:如果不存在则创建
StandardOpenOption.CREATE_NEW
:创建新文件,如果存在则失败
StandardOpenOption.TRUNCATE_EXISTING
:如果存在则截断
StandardOpenOption.APPEND
:追加模式
StandardOpenOption.WRITE
:写访问
StandardOpenOption.READ
:读访问
3.2 大文件流式读写
对于大文件,应该使用流式读写以避免内存问题
3.2.1 使用BufferedReader读取
Path largeFile = Paths.get("largefile.txt");
try (BufferedReader reader = Files.newBufferedReader(largeFile, StandardCharsets.UTF_8)) {
String line;
while ((line = reader.readLine()) != null) {
// 处理每一行
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
方法说明:
newBufferedReader(Path path, Charset cs)
:创建BufferedReader
适合逐行处理大文本文件
3.2.2 使用BufferedWriter写入
Path outputFile = Paths.get("output_large.txt");
try (BufferedWriter writer = Files.newBufferedWriter(outputFile,
StandardCharsets.UTF_8, StandardOpenOption.CREATE)) {
for (int i = 0; i < 10000; i++) {
writer.write("这是第 " + i + " 行
");
}
System.out.println("大文件写入完成");
} catch (IOException e) {
e.printStackTrace();
}
方法说明:
newBufferedWriter(Path path, Charset cs, OpenOption... options)
:创建BufferedWriter
适合逐行写入大文本文件
3.2.3 使用InputStream/OutputStream
// 二进制文件读取
Path binaryFile = Paths.get("image.jpg");
try (InputStream in = Files.newInputStream(binaryFile)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
// 处理读取的数据
}
} catch (IOException e) {
e.printStackTrace();
}
// 二进制文件写入
Path outputBinary = Paths.get("copy.jpg");
try (OutputStream out = Files.newOutputStream(outputBinary,
StandardOpenOption.CREATE)) {
byte[] data = new byte[1024];
// 填充data...
out.write(data);
} catch (IOException e) {
e.printStackTrace();
}
方法说明:
newInputStream(Path path, OpenOption... options)
:创建输入流
newOutputStream(Path path, OpenOption... options)
:创建输出流
适合处理二进制文件或需要更细粒度控制的场景
3.3 随机访问文件
Files类也支持随机访问文件操作:
Path logFile = Paths.get("app.log");
// 使用SeekableByteChannel进行随机访问
try (SeekableByteChannel channel = Files.newByteChannel(logFile,
StandardOpenOption.READ, StandardOpenOption.WRITE)) {
// 移动到文件末尾
channel.position(channel.size());
// 写入日志条目
String logEntry = "New log entry at " + new Date() + "
";
ByteBuffer buffer = ByteBuffer.wrap(logEntry.getBytes(StandardCharsets.UTF_8));
channel.write(buffer);
// 回到文件开头读取
channel.position(0);
buffer.clear();
while (channel.read(buffer) > 0) {
buffer.flip();
System.out.print(StandardCharsets.UTF_8.decode(buffer));
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
方法说明:
newByteChannel(Path path, OpenOption... options)
:创建SeekableByteChannel
支持随机访问和读写操作
比RandomAccessFile更现代和灵活的API
四、高级文件操作
4.1 文件属性操作
Files类提供了丰富的API来访问和修改文件属性
4.1.1 基本属性
Path file = Paths.get("example.txt");
try {
// 文件大小
long size = Files.size(file);
System.out.println("文件大小: " + size + " 字节");
// 最后修改时间
FileTime lastModified = Files.getLastModifiedTime(file);
System.out.println("最后修改时间: " + lastModified);
// 文件所有者
UserPrincipal owner = Files.getOwner(file);
System.out.println("文件所有者: " + owner.getName());
// 文件权限
Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(file);
System.out.println("文件权限: " + permissions);
} catch (IOException e) {
e.printStackTrace();
}
4.1.2 修改属性
Path file = Paths.get("example.txt");
try {
// 设置最后修改时间
FileTime newTime = FileTime.fromMillis(System.currentTimeMillis());
Files.setLastModifiedTime(file, newTime);
// 设置文件所有者(需要权限)
UserPrincipalLookupService lookupService =
file.getFileSystem().getUserPrincipalLookupService();
UserPrincipal newOwner = lookupService.lookupPrincipalByName("username");
Files.setOwner(file, newOwner);
// 设置文件权限(POSIX系统)
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-r-----");
Files.setPosixFilePermissions(file, perms);
} catch (IOException e) {
e.printStackTrace();
}
4.1.3 文件属性视图
Files类支持通过不同的视图访问文件属性:
视图类型 | 描述 |
---|---|
BasicFileAttributeView | 基本文件属性,所有文件系统都支持 |
DosFileAttributeView | DOS相关属性(如隐藏、存档等) |
PosixFileAttributeView | POSIX系统属性(如用户、组、权限等) |
FileOwnerAttributeView | 文件所有者信息 |
AclFileAttributeView | 访问控制列表(ACL) |
UserDefinedAttributeView | 用户自定义属性 |
Path file = Paths.get("example.txt");
// 获取基本文件属性视图
BasicFileAttributeView basicView =
Files.getFileAttributeView(file, BasicFileAttributeView.class);
try {
BasicFileAttributes attrs = basicView.readAttributes();
System.out.println("创建时间: " + attrs.creationTime());
System.out.println("最后访问时间: " + attrs.lastAccessTime());
System.out.println("最后修改时间: " + attrs.lastModifiedTime());
System.out.println("是目录: " + attrs.isDirectory());
System.out.println("是普通文件: " + attrs.isRegularFile());
System.out.println("是符号链接: " + attrs.isSymbolicLink());
System.out.println("大小: " + attrs.size());
} catch (IOException e) {
e.printStackTrace();
}
4.2 目录操作
4.2.1 遍历目录
Path dir = Paths.get(".");
// 简单遍历
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
for (Path entry : stream) {
System.out.println(entry.getFileName());
}
} catch (IOException e) {
e.printStackTrace();
}
// 带过滤器的遍历
try (DirectoryStream<Path> stream =
Files.newDirectoryStream(dir, "*.{java,class}")) {
for (Path entry : stream) {
System.out.println("Java文件: " + entry.getFileName());
}
} catch (IOException e) {
e.printStackTrace();
}
4.2.2 递归遍历目录
Path startDir = Paths.get(".");
// 使用walkFileTree递归遍历
try {
Files.walkFileTree(startDir, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
System.out.println("访问文件: " + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir,
BasicFileAttributes attrs) throws IOException {
System.out.println("进入目录: " + dir);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc)
throws IOException {
System.err.println("访问文件失败: " + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
throws IOException {
System.out.println("离开目录: " + dir);
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
e.printStackTrace();
}
4.2.3 查找文件
Path startDir = Paths.get(".");
// 查找所有.java文件
try {
Files.find(startDir, Integer.MAX_VALUE,
(path, attrs) -> path.toString().endsWith(".java"))
.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
4.3 文件监控
Files类可以与WatchService API结合使用来监控文件系统变化:
Path dir = Paths.get(".");
try {
WatchService watcher = FileSystems.getDefault().newWatchService();
// 注册感兴趣的事件类型
dir.register(watcher,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);
System.out.println("开始监控目录: " + dir);
while (true) {
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException e) {
return;
}
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
// 处理OVERFLOW事件
if (kind == StandardWatchEventKinds.OVERFLOW) {
continue;
}
// 获取文件名
@SuppressWarnings("unchecked")
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path filename = ev.context();
System.out.println("事件类型: " + kind + ", 文件: " + filename);
}
// 重置key
boolean valid = key.reset();
if (!valid) {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
五、进阶主题与最佳实践
5.1 文件锁
Files类可以与FileLock结合使用实现文件锁定:
Path file = Paths.get("shared.txt");
try (RandomAccessFile raf = new RandomAccessFile(file.toFile(), "rw");
FileChannel channel = raf.getChannel()) {
// 获取独占锁
FileLock lock = channel.lock();
try {
System.out.println("获得文件锁");
// 执行需要独占访问的操作
Thread.sleep(5000); // 模拟长时间操作
} finally {
lock.release();
System.out.println("释放文件锁");
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
5.2 内存映射文件
Files类支持内存映射文件操作,提高大文件访问性能:
Path largeFile = Paths.get("large.dat");
try (FileChannel channel = FileChannel.open(largeFile,
StandardOpenOption.READ, StandardOpenOption.WRITE)) {
// 将文件映射到内存
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_WRITE, 0, channel.size());
// 读取/修改缓冲区内容
while (buffer.hasRemaining()) {
byte b = buffer.get();
// 处理字节...
}
// 修改内容
buffer.position(0);
buffer.put("New content".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
5.3 文件操作原子性与一致性
Files类中的某些操作提供了原子性保证:
原子移动:使用StandardCopyOption.ATOMIC_MOVE
选项的move操作
原子创建:createFile
和createTempFile
方法保证原子性
原子写入:使用适当的选项组合可以保证写入的原子性
5.4 性能考虑
小文件操作:对于小文件,readAllBytes
和readAllLines
方法最简单
大文件操作:使用流式API或内存映射文件
缓冲区大小:对于IO操作,适当设置缓冲区大小(通常8KB-32KB)
批量操作:尽可能减少文件系统调用次数
5.5 异常处理最佳实践
文件操作中常见的异常及处理建议:
异常类型 | 原因 | 处理建议 |
---|---|---|
NoSuchFileException | 文件不存在 | 检查文件路径,或先调用exists()检查 |
FileAlreadyExistsException | 文件已存在 | 根据业务逻辑决定:覆盖、跳过或重命名 |
AccessDeniedException | 权限不足 | 检查文件权限,或提示用户 |
DirectoryNotEmptyException | 目录不为空 | 先清空目录内容或使用其他策略 |
IOException | 通用IO异常 | 根据具体错误信息处理,记录详细日志 |
5.6 跨平台考虑
路径分隔符:使用FileSystems.getDefault().getSeparator()
获取平台分隔符
文件系统特性:检查文件系统支持的特性,如符号链接、硬链接等
大小写敏感:注意不同平台对文件名大小写的处理不同
权限模型:Windows和Unix-like系统的权限模型不同
六、实战案例
6.1 文件搜索工具
public class FileSearchUtil {
/**
* 在指定目录及其子目录中搜索包含指定文本的文件
* @param rootDir 搜索根目录
* @param textToFind 要查找的文本
* @return 匹配的文件列表
*/
public static List<Path> searchFiles(Path rootDir, String textToFind)
throws IOException {
List<Path> result = new ArrayList<>();
Files.walkFileTree(rootDir, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
if (containsText(file, textToFind)) {
result.add(file);
}
return FileVisitResult.CONTINUE;
}
});
return result;
}
private static boolean containsText(Path file, String text) throws IOException {
if (!Files.isRegularFile(file)) {
return false;
}
try (BufferedReader reader = Files.newBufferedReader(file)) {
String line;
while ((line = reader.readLine()) != null) {
if (line.contains(text)) {
return true;
}
}
}
return false;
}
public static void main(String[] args) {
try {
Path startDir = Paths.get(".");
String searchText = "Files工具类";
List<Path> foundFiles = searchFiles(startDir, searchText);
System.out.println("找到 " + foundFiles.size() + " 个匹配文件:");
foundFiles.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
}
}
6.2 文件备份工具
public class FileBackupUtil {
/**
* 备份指定目录到目标位置
* @param sourceDir 源目录
* @param backupDir 备份目录
* @throws IOException 如果备份过程中发生IO错误
*/
public static void backupDirectory(Path sourceDir, Path backupDir)
throws IOException {
// 创建备份目录
Files.createDirectories(backupDir);
// 遍历源目录
Files.walk(sourceDir)
.forEach(source -> {
try {
Path target = backupDir.resolve(sourceDir.relativize(source));
// 如果是目录,创建目录
if (Files.isDirectory(source)) {
Files.createDirectories(target);
}
// 如果是文件,复制文件
else {
Files.copy(source, target,
StandardCopyOption.REPLACE_EXISTING,
StandardCopyOption.COPY_ATTRIBUTES);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
public static void main(String[] args) {
try {
Path source = Paths.get("data");
Path backup = Paths.get("backup_" +
LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
System.out.println("开始备份: " + source + " -> " + backup);
backupDirectory(source, backup);
System.out.println("备份完成");
} catch (IOException e) {
e.printStackTrace();
}
}
}
6.3 文件加密工具
public class FileEncryptor {
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private static final int IV_SIZE = 16;
/**
* 加密文件
* @param source 源文件
* @param target 目标文件
* @param key 加密密钥
* @throws IOException IO异常
* @throws GeneralSecurityException 安全异常
*/
public static void encrypt(Path source, Path target, SecretKey key)
throws IOException, GeneralSecurityException {
// 生成初始化向量
byte[] iv = new byte[IV_SIZE];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
// 初始化加密器
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
try (InputStream in = Files.newInputStream(source);
OutputStream out = Files.newOutputStream(target,
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
// 写入IV
out.write(iv);
// 加密数据
try (CipherOutputStream cipherOut = new CipherOutputStream(out, cipher)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
cipherOut.write(buffer, 0, bytesRead);
}
}
}
}
/**
* 解密文件
* @param source 源文件
* @param target 目标文件
* @param key 解密密钥
* @throws IOException IO异常
* @throws GeneralSecurityException 安全异常
*/
public static void decrypt(Path source, Path target, SecretKey key)
throws IOException, GeneralSecurityException {
try (InputStream in = Files.newInputStream(source)) {
// 读取IV
byte[] iv = new byte[IV_SIZE];
if (in.read(iv) != IV_SIZE) {
throw new IOException("无效的加密文件格式");
}
IvParameterSpec ivSpec = new IvParameterSpec(iv);
// 初始化解密器
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
try (OutputStream out = Files.newOutputStream(target,
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
CipherInputStream cipherIn = new CipherInputStream(in, cipher)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = cipherIn.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
}
}
public static void main(String[] args) {
try {
// 生成密钥
KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM);
keyGen.init(256);
SecretKey key = keyGen.generateKey();
Path original = Paths.get("original.txt");
Path encrypted = Paths.get("encrypted.aes");
Path decrypted = Paths.get("decrypted.txt");
// 加密文件
encrypt(original, encrypted, key);
System.out.println("文件加密完成");
// 解密文件
decrypt(encrypted, decrypted, key);
System.out.println("文件解密完成");
// 验证
byte[] originalBytes = Files.readAllBytes(original);
byte[] decryptedBytes = Files.readAllBytes(decrypted);
System.out.println("解密验证: " + Arrays.equals(originalBytes, decryptedBytes));
} catch (Exception e) {
e.printStackTrace();
}
}
}
七、总结
Java的Files工具类提供了强大而灵活的文件操作API,涵盖了从基础到高级的各种文件操作需求。通过本教程,我们详细探讨了:
基础操作:文件检查、创建、删除、复制和移动
内容读写:小文件和大文件的读写策略,包括流式处理和内存映射
高级特性:文件属性操作、目录遍历、文件监控等
进阶主题:文件锁、原子操作、性能优化和跨平台考虑
实战案例:文件搜索、备份和加密工具的实现
Files类的设计体现了现代Java API的特点:
方法命名清晰一致
异常处理明确
支持链式调用
与NIO.2其他组件良好集成
提供丰富的选项和灵活性
在实际开发中,应根据具体需求选择合适的API:
对于简单操作,使用便捷方法如readAllBytes
和write
对于大文件或性能敏感场景,使用流式API或内存映射
需要原子性保证时,使用适当的选项
跨平台应用注意文件系统差异
通过合理使用Files工具类,可以编写出健壮、高效且可维护的文件操作代码,满足各种复杂的业务需求。
喜欢的点个已关注,想了解更多的可以已关注微信公众号 “Eric的技术杂货库” ,提供更多的干货以及资料下载保存!
暂无评论内容