轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java

    • 核心

      • Java8--Lambda 表达式、Stream 和时间 API
      • Java集合
      • Java IO
      • Java 文件操作
        • 一、概述
        • 二、快速对比
        • 三、文件对象创建
          • 1、传统方式(File)
          • 2、2 方式(Path/Files)
          • 3、与 Path 相互转换
        • 四、获取文件信息
          • 1、传统方式
          • 2、2 方式
        • 五、文件属性操作
          • 1、读取文件属性
          • 2、设置文件属性
          • 3、探测文件类型
        • 六、读写文件内容
          • 1、小文件读写(一次性加载)
          • 2、大文件读写(流式处理)
        • 七、创建、复制、移动和删除
          • 1、创建操作
          • 2、复制操作
          • 3、移动操作
          • 4、删除操作
        • 八、目录遍历
          • 1、传统方式
          • 2、2 方式
          • 3、使用 FileVisitor 模式
        • 九、路径操作
          • 1、路径解析和组合
          • 2、路径匹配
        • 十、随机访问
        • 十一、文件监视服务
        • 十二、实战示例
          • 1、文件备份工具
          • 2、目录同步工具
          • 3、文件搜索工具
          • 4、大文件分割与合并
        • 十三、工具类库
          • 1、Commons IO
        • 十四、性能优化建议
          • 1、选择合适的 API
          • 2、使用合适的缓冲区大小
          • 3、批量操作优化
          • 4、避免频繁的文件系统调用
        • 十五、异常处理最佳实践
          • 1、使用 try-with-resources
          • 2、具体的异常处理
          • 3、防御性编程
        • 十六、总结
      • Java 网络编程
      • Java运行期动态能力
      • Java可插入注解处理器
      • Java基准测试(JMH)
      • Java性能分析(Profiler)
      • Java调试(JDI与JDWP)
      • Java管理与监控(JMX)
      • Java加密体系(JCA)
      • Java服务发现(SPI)
      • Java随机数生成研究
      • Java数据库连接(JDBC)
      • Java历代版本新特性
      • 写好Java Doc
      • 聊聊classpath及其资源获取
    • 并发

    • 经验

    • JVM

    • 企业应用

  • Spring

  • 其他语言

  • 工具

  • 后端
  • Java
  • 核心
轩辕李
2021-09-12
目录

Java 文件操作

# 一、概述

文件和目录操作是每个编程语言中的基础功能。Java 提供了两套文件操作 API:

  • 传统 I/O(java.io.File):Java 7 之前的主要方式,概念简单,使用方便
  • NIO.2(java.nio.file):Java 7 引入,功能更强大,性能更好,API 更现代化

在开始之前,建议先了解 Java IO 的基础知识。

# 二、快速对比

功能 传统 I/O (File) NIO.2 (Path/Files)
文件路径表示 File 对象 Path 对象
文件操作 File 类方法 Files 工具类
异常处理 返回布尔值 抛出具体异常
符号链接 不支持 原生支持
文件属性 基础属性 丰富的属性支持
监视服务 不支持 WatchService
异步操作 不支持 支持异步 I/O

# 三、文件对象创建

# 1、传统方式(File)

// 创建文件对象
File file = new File("d:/test.txt");
File file2 = new File("d:/", "test.txt");
File file3 = new File(new File("d:/"), "test.txt");

// 创建目录对象
File dir = new File("d:/data");

// 判断类型
if (file.isFile()) {
    System.out.println("这是一个文件");
}
if (dir.isDirectory()) {
    System.out.println("这是一个目录");
}

// 常用判断方法
file.exists();        // 是否存在
file.isAbsolute();    // 是否绝对路径
file.isHidden();      // 是否隐藏
file.canRead();       // 是否可读
file.canWrite();      // 是否可写
file.canExecute();    // 是否可执行

# 2、2 方式(Path/Files)

// 创建路径对象
Path path = Paths.get("d:/test.txt");
Path path2 = Paths.get("d:/", "test.txt");
Path path3 = Path.of("d:/", "test.txt");  // Java 11+

// 创建目录路径
Path dir = Paths.get("d:/data");

// 判断类型
if (Files.isRegularFile(path)) {
    System.out.println("这是一个文件");
}
if (Files.isDirectory(dir)) {
    System.out.println("这是一个目录");
}

// 丰富的判断方法
Files.exists(path);              // 是否存在
Files.notExists(path);           // 是否不存在
Files.isReadable(path);          // 是否可读
Files.isWritable(path);          // 是否可写
Files.isExecutable(path);        // 是否可执行
Files.isHidden(path);            // 是否隐藏
Files.isSymbolicLink(path);      // 是否符号链接
Files.isSameFile(path, path2);   // 是否同一文件

# 3、与 Path 相互转换

// File 转 Path
Path path = new File("d:/test.txt").toPath();

// Path 转 File
File file = Paths.get("d:/test.txt").toFile();

# 四、获取文件信息

# 1、传统方式

File file = new File("d:/test.txt");

// 基本信息
String name = file.getName();                  // 文件名
String absolutePath = file.getAbsolutePath();  // 绝对路径
String parent = file.getParent();              // 父目录路径
File parentFile = file.getParentFile();        // 父目录对象
long length = file.length();                   // 文件大小(字节)
long lastModified = file.lastModified();       // 最后修改时间

// 规范路径(解析 . 和 .. 符号)
try {
    String canonicalPath = file.getCanonicalPath();
} catch (IOException e) {
    e.printStackTrace();
}

// 磁盘空间信息
long totalSpace = file.getTotalSpace();      // 总空间
long freeSpace = file.getFreeSpace();        // 剩余空间
long usableSpace = file.getUsableSpace();    // 可用空间

# 2、2 方式

Path path = Paths.get("d:/test.txt");

// 基本信息
Path fileName = path.getFileName();           // 文件名
Path parent = path.getParent();               // 父路径
Path root = path.getRoot();                   // 根路径
int nameCount = path.getNameCount();          // 路径元素数量

// 文件大小
long size = Files.size(path);

// 规范化路径
Path normalized = path.normalize();           // 解析 . 和 ..
Path real = path.toRealPath();               // 获取真实路径

// 最后修改时间
FileTime lastModified = Files.getLastModifiedTime(path);
Instant instant = lastModified.toInstant();

# 五、文件属性操作

# 1、读取文件属性

Path path = Paths.get("d:/test.txt");

// 读取基本属性
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
System.out.println("创建时间: " + attrs.creationTime());
System.out.println("最后修改: " + attrs.lastModifiedTime());
System.out.println("最后访问: " + attrs.lastAccessTime());
System.out.println("文件大小: " + attrs.size());
System.out.println("是否目录: " + attrs.isDirectory());
System.out.println("是否文件: " + attrs.isRegularFile());

// Windows 系统特有属性
if (System.getProperty("os.name").startsWith("Windows")) {
    DosFileAttributes dosAttrs = Files.readAttributes(path, DosFileAttributes.class);
    System.out.println("是否隐藏: " + dosAttrs.isHidden());
    System.out.println("是否系统文件: " + dosAttrs.isSystem());
    System.out.println("是否只读: " + dosAttrs.isReadOnly());
    System.out.println("是否归档: " + dosAttrs.isArchive());
}

// Unix/Linux 系统特有属性
if (System.getProperty("os.name").contains("nix") || 
    System.getProperty("os.name").contains("nux")) {
    PosixFileAttributes posixAttrs = Files.readAttributes(path, PosixFileAttributes.class);
    System.out.println("所有者: " + posixAttrs.owner());
    System.out.println("组: " + posixAttrs.group());
    System.out.println("权限: " + PosixFilePermissions.toString(posixAttrs.permissions()));
}

# 2、设置文件属性

Path path = Paths.get("d:/test.txt");

// 设置最后修改时间
FileTime newTime = FileTime.from(Instant.now());
Files.setLastModifiedTime(path, newTime);

// Windows 系统设置属性
if (System.getProperty("os.name").startsWith("Windows")) {
    Files.setAttribute(path, "dos:hidden", true);
    Files.setAttribute(path, "dos:readonly", true);
}

// Unix/Linux 系统设置权限
if (System.getProperty("os.name").contains("nix")) {
    Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-r--r--");
    Files.setPosixFilePermissions(path, perms);
    
    // 设置所有者
    UserPrincipal owner = path.getFileSystem()
        .getUserPrincipalLookupService()
        .lookupPrincipalByName("username");
    Files.setOwner(path, owner);
}

# 3、探测文件类型

// 根据文件扩展名探测 MIME 类型
String mimeType = Files.probeContentType(Paths.get("test.jpg"));   // image/jpeg
System.out.println("MIME 类型: " + mimeType);

// 常见文件类型
Files.probeContentType(Paths.get("test.txt"));    // text/plain
Files.probeContentType(Paths.get("test.html"));   // text/html
Files.probeContentType(Paths.get("test.pdf"));    // application/pdf
Files.probeContentType(Paths.get("test.mp4"));    // video/mp4
Files.probeContentType(Paths.get("test.xlsx"));   // application/vnd.openxmlformats-officedocument.spreadsheetml.sheet

// 注意:该方法基于文件扩展名,不检查实际内容

# 六、读写文件内容

# 1、小文件读写(一次性加载)

Path path = Paths.get("d:/test.txt");

// 读取文本文件
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
String content = Files.readString(path);  // Java 11+

// 读取二进制文件
byte[] bytes = Files.readAllBytes(path);

// 写入文本文件
List<String> linesToWrite = Arrays.asList("第一行", "第二行", "第三行");
Files.write(path, linesToWrite, StandardCharsets.UTF_8);
Files.writeString(path, "文件内容");  // Java 11+

// 写入二进制文件
byte[] data = "Hello World".getBytes(StandardCharsets.UTF_8);
Files.write(path, data);

// 追加内容
Files.write(path, linesToWrite, StandardCharsets.UTF_8, 
    StandardOpenOption.CREATE,    // 文件不存在则创建
    StandardOpenOption.APPEND);   // 追加模式

# 2、大文件读写(流式处理)

Path path = Paths.get("d:/large-file.txt");

// 使用 BufferedReader 读取大文件
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
    String line;
    while ((line = reader.readLine()) != null) {
        // 处理每一行
        System.out.println(line);
    }
}

// 使用 Stream API 读取(Java 8+)
try (Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)) {
    lines.filter(line -> line.contains("关键字"))
         .forEach(System.out::println);
}

// 使用 BufferedWriter 写入大文件
try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8,
        StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
    for (int i = 0; i < 1000000; i++) {
        writer.write("Line " + i);
        writer.newLine();
    }
}

// 使用输入输出流
try (InputStream in = Files.newInputStream(path);
     OutputStream out = Files.newOutputStream(Paths.get("d:/output.txt"))) {
    byte[] buffer = new byte[4096];
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        out.write(buffer, 0, bytesRead);
    }
}

# 七、创建、复制、移动和删除

# 1、创建操作

// 创建文件
Path file = Paths.get("d:/new-file.txt");
Files.createFile(file);  // 文件已存在会抛异常

// 安全创建文件(先检查)
if (!Files.exists(file)) {
    Files.createFile(file);
}

// 创建目录
Path dir = Paths.get("d:/new-dir");
Files.createDirectory(dir);  // 父目录必须存在

// 创建多级目录
Path dirs = Paths.get("d:/parent/child/grandchild");
Files.createDirectories(dirs);  // 自动创建所有不存在的父目录

// 创建临时文件
Path tempFile = Files.createTempFile("prefix-", ".txt");
Path tempFileInDir = Files.createTempFile(Paths.get("d:/"), "temp-", ".txt");

// 创建临时目录
Path tempDir = Files.createTempDirectory("temp-dir-");

# 2、复制操作

Path source = Paths.get("d:/source.txt");
Path target = Paths.get("d:/target.txt");

// 基本复制
Files.copy(source, target);

// 复制并覆盖已存在的文件
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);

// 复制文件属性
Files.copy(source, target, 
    StandardCopyOption.REPLACE_EXISTING,
    StandardCopyOption.COPY_ATTRIBUTES);

// 复制目录(不包含内容)
Path sourceDir = Paths.get("d:/source-dir");
Path targetDir = Paths.get("d:/target-dir");
Files.copy(sourceDir, targetDir);

// 递归复制目录及其内容
Files.walk(sourceDir)
    .forEach(sourcePath -> {
        try {
            Path targetPath = targetDir.resolve(sourceDir.relativize(sourcePath));
            Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            e.printStackTrace();
        }
    });

# 3、移动操作

Path source = Paths.get("d:/old-name.txt");
Path target = Paths.get("d:/new-name.txt");

// 移动/重命名文件
Files.move(source, target);

// 移动并覆盖
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);

// 原子移动(要么完全成功,要么完全失败)
Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);

// 移动目录
Path sourceDir = Paths.get("d:/old-dir");
Path targetDir = Paths.get("e:/new-dir");
Files.move(sourceDir, targetDir, StandardCopyOption.REPLACE_EXISTING);

# 4、删除操作

Path file = Paths.get("d:/file-to-delete.txt");

// 删除文件(文件不存在会抛异常)
Files.delete(file);

// 安全删除(文件不存在不会抛异常)
Files.deleteIfExists(file);

// 删除目录(目录必须为空)
Path dir = Paths.get("d:/empty-dir");
Files.delete(dir);

// 递归删除目录及其内容
Path dirToDelete = Paths.get("d:/dir-to-delete");
Files.walk(dirToDelete)
    .sorted(Comparator.reverseOrder())  // 先删除子文件/目录
    .map(Path::toFile)
    .forEach(File::delete);

// 使用 FileVisitor 递归删除
Files.walkFileTree(dirToDelete, new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        Files.delete(file);
        return FileVisitResult.CONTINUE;
    }
    
    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
        Files.delete(dir);
        return FileVisitResult.CONTINUE;
    }
});

# 八、目录遍历

# 1、传统方式

File dir = new File("d:/data");

// 获取文件名列表
String[] fileNames = dir.list();

// 使用过滤器
String[] txtFiles = dir.list((dir1, name) -> name.endsWith(".txt"));

// 获取文件对象列表
File[] files = dir.listFiles();

// 使用文件过滤器
File[] readableFiles = dir.listFiles(File::canRead);
File[] largeFiles = dir.listFiles(file -> file.length() > 1024 * 1024);  // > 1MB

# 2、2 方式

Path dir = Paths.get("d:/data");

// 列出目录内容(非递归)
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
    for (Path entry : stream) {
        System.out.println(entry.getFileName());
    }
}

// 使用过滤器
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.txt")) {
    stream.forEach(System.out::println);
}

// 使用 Stream API(Java 8+)
try (Stream<Path> paths = Files.list(dir)) {
    paths.filter(Files::isRegularFile)
         .filter(p -> p.toString().endsWith(".java"))
         .forEach(System.out::println);
}

// 递归遍历(深度优先)
try (Stream<Path> paths = Files.walk(dir)) {
    paths.filter(Files::isRegularFile)
         .forEach(System.out::println);
}

// 限制遍历深度
try (Stream<Path> paths = Files.walk(dir, 2)) {  // 最多遍历2层
    paths.forEach(System.out::println);
}

// 查找文件
try (Stream<Path> paths = Files.find(dir, Integer.MAX_VALUE,
        (path, attrs) -> attrs.isRegularFile() && path.toString().endsWith(".txt"))) {
    paths.forEach(System.out::println);
}

# 3、使用 FileVisitor 模式

Path startDir = Paths.get("d:/project");

Files.walkFileTree(startDir, new SimpleFileVisitor<Path>() {
    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
        System.out.println("进入目录: " + dir);
        return FileVisitResult.CONTINUE;
    }
    
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
        System.out.println("访问文件: " + file);
        if (file.toString().endsWith(".class")) {
            // 跳过 .class 文件的子目录
            return FileVisitResult.SKIP_SUBTREE;
        }
        return FileVisitResult.CONTINUE;
    }
    
    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) {
        System.err.println("访问失败: " + file);
        return FileVisitResult.CONTINUE;
    }
    
    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
        System.out.println("离开目录: " + dir);
        return FileVisitResult.CONTINUE;
    }
});

# 九、路径操作

# 1、路径解析和组合

Path base = Paths.get("/home/user");

// 解析子路径
Path resolved = base.resolve("documents/file.txt");  // /home/user/documents/file.txt

// 解析兄弟路径
Path sibling = base.resolveSibling("guest");  // /home/guest

// 获取相对路径
Path p1 = Paths.get("/home/user/docs");
Path p2 = Paths.get("/home/user/downloads/files");
Path relative = p1.relativize(p2);  // ../downloads/files

// 规范化路径(移除 . 和 ..)
Path path = Paths.get("/home/./user/../user/docs");
Path normalized = path.normalize();  // /home/user/docs

// 转换为绝对路径
Path relativePath = Paths.get("../file.txt");
Path absolutePath = relativePath.toAbsolutePath();

// 获取真实路径(解析符号链接)
Path realPath = path.toRealPath();

# 2、路径匹配

Path path = Paths.get("/home/user/documents/report.pdf");

// 使用 PathMatcher
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.pdf");
boolean matches = matcher.matches(path.getFileName());  // true

// 支持的语法
PathMatcher globMatcher = FileSystems.getDefault().getPathMatcher("glob:**/*.{java,class}");
PathMatcher regexMatcher = FileSystems.getDefault().getPathMatcher("regex:.*\\.txt");

// 在遍历中使用
try (Stream<Path> paths = Files.walk(Paths.get("."))) {
    paths.filter(globMatcher::matches)
         .forEach(System.out::println);
}

# 十、随机访问

RandomAccessFile 支持对文件的随机读写访问,核心是 seek() 方法定位文件指针。

// 读取文件的特定部分
try (RandomAccessFile raf = new RandomAccessFile("d:/large-file.dat", "r")) {
    // 跳到第 1024 字节
    raf.seek(1024);
    
    // 读取 100 字节
    byte[] buffer = new byte[100];
    raf.read(buffer);
    
    // 获取当前文件指针位置
    long position = raf.getFilePointer();
}

// 在文件末尾追加内容
try (RandomAccessFile raf = new RandomAccessFile("d:/log.txt", "rw")) {
    // 移到文件末尾
    raf.seek(raf.length());
    
    // 追加内容
    raf.writeBytes("\n新的日志条目");
}

// 实现简单的文件分块读取
public void readFileInChunks(String filename, int chunkSize) throws IOException {
    try (RandomAccessFile raf = new RandomAccessFile(filename, "r")) {
        long fileSize = raf.length();
        long chunks = (fileSize + chunkSize - 1) / chunkSize;
        
        for (long i = 0; i < chunks; i++) {
            raf.seek(i * chunkSize);
            byte[] buffer = new byte[chunkSize];
            int bytesRead = raf.read(buffer);
            // 处理读取的数据
            processChunk(buffer, bytesRead);
        }
    }
}

访问模式说明:

  • "r":只读模式
  • "rw":读写模式,文件不存在会创建
  • "rws":读写模式,每次写入同步内容和元数据
  • "rwd":读写模式,每次写入只同步内容

# 十一、文件监视服务

NIO.2 提供了 WatchService 来监视文件系统的变化:

// 创建监视服务
WatchService watchService = FileSystems.getDefault().newWatchService();

// 注册要监视的目录
Path dir = Paths.get("d:/watched-dir");
dir.register(watchService, 
    StandardWatchEventKinds.ENTRY_CREATE,
    StandardWatchEventKinds.ENTRY_DELETE,
    StandardWatchEventKinds.ENTRY_MODIFY);

// 处理文件系统事件
while (true) {
    WatchKey key;
    try {
        key = watchService.take();  // 阻塞等待事件
    } catch (InterruptedException e) {
        return;
    }
    
    for (WatchEvent<?> event : key.pollEvents()) {
        WatchEvent.Kind<?> kind = event.kind();
        
        if (kind == StandardWatchEventKinds.OVERFLOW) {
            continue;
        }
        
        WatchEvent<Path> ev = (WatchEvent<Path>) event;
        Path filename = ev.context();
        
        System.out.printf("事件 %s: %s%n", kind.name(), filename);
    }
    
    boolean valid = key.reset();
    if (!valid) {
        break;
    }
}

# 十二、实战示例

# 1、文件备份工具

public class FileBackup {
    public static void backupFile(Path source) throws IOException {
        // 生成备份文件名
        String timestamp = LocalDateTime.now()
            .format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
        String fileName = source.getFileName().toString();
        String backupName = fileName.replaceFirst("(\\.[^.]+)?$", "_" + timestamp + "$1");
        
        Path backup = source.resolveSibling(backupName);
        
        // 复制文件并保留属性
        Files.copy(source, backup, 
            StandardCopyOption.COPY_ATTRIBUTES,
            StandardCopyOption.REPLACE_EXISTING);
        
        System.out.println("备份创建: " + backup);
    }
}

# 2、目录同步工具

public class DirectorySync {
    public static void syncDirectories(Path source, Path target) throws IOException {
        // 确保目标目录存在
        Files.createDirectories(target);
        
        // 遍历源目录
        Files.walk(source).forEach(sourcePath -> {
            try {
                Path targetPath = target.resolve(source.relativize(sourcePath));
                
                if (Files.isDirectory(sourcePath)) {
                    Files.createDirectories(targetPath);
                } else {
                    // 只复制较新的文件
                    if (!Files.exists(targetPath) || 
                        Files.getLastModifiedTime(sourcePath).compareTo(
                            Files.getLastModifiedTime(targetPath)) > 0) {
                        Files.copy(sourcePath, targetPath, 
                            StandardCopyOption.REPLACE_EXISTING,
                            StandardCopyOption.COPY_ATTRIBUTES);
                        System.out.println("同步: " + targetPath);
                    }
                }
            } catch (IOException e) {
                System.err.println("同步失败: " + sourcePath);
            }
        });
    }
}

# 3、文件搜索工具

public class FileSearch {
    public static List<Path> searchFiles(Path startDir, String pattern) throws IOException {
        List<Path> result = new ArrayList<>();
        PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);
        
        Files.walkFileTree(startDir, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
                if (matcher.matches(file.getFileName())) {
                    result.add(file);
                }
                return FileVisitResult.CONTINUE;
            }
            
            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) {
                System.err.println("无法访问: " + file);
                return FileVisitResult.CONTINUE;
            }
        });
        
        return result;
    }
    
    // 使用示例
    public static void main(String[] args) throws IOException {
        List<Path> javaFiles = searchFiles(Paths.get("."), "*.java");
        javaFiles.forEach(System.out::println);
    }
}

# 4、大文件分割与合并

public class FileSplitter {
    // 分割文件
    public static void splitFile(Path source, int chunkSize) throws IOException {
        byte[] buffer = new byte[chunkSize];
        
        try (InputStream input = Files.newInputStream(source)) {
            int partNumber = 1;
            int bytesRead;
            
            while ((bytesRead = input.read(buffer)) > 0) {
                String partName = source.getFileName() + ".part" + partNumber++;
                Path partFile = source.resolveSibling(partName);
                
                Files.write(partFile, buffer, 0, bytesRead);
                System.out.println("创建分片: " + partFile);
            }
        }
    }
    
    // 合并文件
    public static void mergeFiles(Path targetFile, Path... parts) throws IOException {
        try (OutputStream output = Files.newOutputStream(targetFile, 
                StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
            
            for (Path part : parts) {
                Files.copy(part, output);
                System.out.println("合并: " + part);
            }
        }
    }
}

# 十三、工具类库

# 1、Commons IO

Commons IO 提供了许多实用的文件操作工具:

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version>
</dependency>

常用功能示例:

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.*;

// 文件操作
File file = new File("d:/test.txt");

// 读写文件
String content = FileUtils.readFileToString(file, "UTF-8");
FileUtils.writeStringToFile(file, "内容", "UTF-8");
List<String> lines = FileUtils.readLines(file, "UTF-8");

// 复制文件/目录
FileUtils.copyFile(srcFile, destFile);
FileUtils.copyDirectory(srcDir, destDir);
FileUtils.copyDirectoryToDirectory(srcDir, destDir);

// 移动文件/目录
FileUtils.moveFile(srcFile, destFile);
FileUtils.moveDirectory(srcDir, destDir);

// 删除文件/目录
FileUtils.deleteQuietly(file);  // 静默删除
FileUtils.deleteDirectory(directory);  // 递归删除目录
FileUtils.cleanDirectory(directory);  // 清空目录

// 创建目录
FileUtils.forceMkdir(directory);  // 强制创建目录,包括父目录

// 文件大小
long size = FileUtils.sizeOf(file);
String displaySize = FileUtils.byteCountToDisplaySize(size);  // 如: "1.2 GB"

// 文件比较
boolean isEqual = FileUtils.contentEquals(file1, file2);

// 文件过滤
Collection<File> files = FileUtils.listFiles(
    directory,
    new SuffixFileFilter(".java"),
    TrueFileFilter.INSTANCE
);

// 文件名操作
String extension = FilenameUtils.getExtension("file.txt");  // "txt"
String baseName = FilenameUtils.getBaseName("file.txt");    // "file"
String path = FilenameUtils.getFullPath("/home/user/file.txt");  // "/home/user/"

# 十四、性能优化建议

# 1、选择合适的 API

// 小文件(< 10MB):一次性读取
String content = Files.readString(path);

// 中等文件(10MB - 100MB):使用缓冲流
try (BufferedReader reader = Files.newBufferedReader(path)) {
    // 逐行处理
}

// 大文件(> 100MB):使用内存映射或分块读取
try (RandomAccessFile raf = new RandomAccessFile(file, "r");
     FileChannel channel = raf.getChannel()) {
    MappedByteBuffer buffer = channel.map(
        FileChannel.MapMode.READ_ONLY, 0, channel.size());
}

# 2、使用合适的缓冲区大小

// 默认缓冲区通常是 8KB,对于大文件可以增大
int bufferSize = 64 * 1024;  // 64KB
try (BufferedInputStream bis = new BufferedInputStream(
        new FileInputStream(file), bufferSize)) {
    // 处理
}

# 3、批量操作优化

// 批量删除文件时,使用并行流
Files.walk(directory)
    .parallel()
    .filter(Files::isRegularFile)
    .forEach(path -> {
        try {
            Files.delete(path);
        } catch (IOException e) {
            // 处理异常
        }
    });

# 4、避免频繁的文件系统调用

// 缓存文件属性,避免重复调用
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
long size = attrs.size();
FileTime lastModified = attrs.lastModifiedTime();
boolean isDirectory = attrs.isDirectory();

# 十五、异常处理最佳实践

# 1、使用 try-with-resources

// 自动关闭资源
try (Stream<Path> paths = Files.walk(directory);
     BufferedWriter writer = Files.newBufferedWriter(output)) {
    paths.filter(Files::isRegularFile)
         .map(Path::toString)
         .forEach(line -> {
             try {
                 writer.write(line);
                 writer.newLine();
             } catch (IOException e) {
                 // 处理写入异常
             }
         });
}

# 2、具体的异常处理

try {
    Files.createDirectory(path);
} catch (FileAlreadyExistsException e) {
    // 文件已存在
    System.err.println("目录已存在: " + path);
} catch (NoSuchFileException e) {
    // 父目录不存在
    System.err.println("父目录不存在: " + path.getParent());
} catch (AccessDeniedException e) {
    // 权限不足
    System.err.println("权限不足: " + path);
} catch (IOException e) {
    // 其他 I/O 错误
    System.err.println("创建目录失败: " + e.getMessage());
}

# 3、防御性编程

public static void safeDelete(Path path) {
    try {
        if (Files.exists(path)) {
            if (Files.isDirectory(path)) {
                // 确保目录为空
                try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
                    if (stream.iterator().hasNext()) {
                        System.err.println("目录不为空: " + path);
                        return;
                    }
                }
            }
            Files.delete(path);
        }
    } catch (IOException e) {
        System.err.println("删除失败: " + path + ", 原因: " + e.getMessage());
    }
}

# 十六、总结

Java 文件操作提供了两套 API 体系:

  1. 传统 I/O(File):

    • 简单直观,适合基础操作
    • 广泛使用,兼容性好
    • 功能有限,错误处理较弱
  2. NIO.2(Path/Files):

    • 功能强大,支持更多特性
    • 更好的异常处理
    • 支持异步操作和文件监视
    • 推荐在新项目中使用

选择建议:

  • 简单操作:使用传统 I/O
  • 复杂需求:使用 NIO.2
  • 大文件处理:考虑 RandomAccessFile 或内存映射
  • 跨平台:注意系统差异,做好兼容处理

记住:文件操作涉及系统资源,务必正确关闭资源,处理好异常!

祝你变得更强!

编辑 (opens new window)
#Java文件操作
上次更新: 2025/08/15
Java IO
Java 网络编程

← Java IO Java 网络编程→

最近更新
01
AI时代的编程心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code实战之供应商切换工具
08-18
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式