JAVA重点基础、进阶知识及易错点总结(13)File 类 + 路径操作

🚀 Java 巩固进阶 · 第13天

主题:File 类 + 路径操作 ------ IO 体系的第一块基石

📅 进度概览 :从今天起,我们正式进入 Java IO 流体系 。第一站:java.io.File

💡 核心价值

  • 文件操作基石File 是操作文件/目录元数据的唯一入口,后续所有流(Stream)都依赖它定位资源。
  • SpringBoot 实战必备 :文件上传存储、配置文件加载、日志目录初始化,处处可见 File 的身影。
  • 路径兼容性:掌握跨平台路径写法,避免 "在我机器上能跑,上线就报错" 的经典坑。
  • 递归思维训练:文件遍历是理解递归的最佳场景,也是面试高频考点。

一、File 类本质:它到底能做什么?

1. 核心定位

复制代码
┌─────────────────────────────────┐
│  📁 File 类                      │
├─────────────────────────────────┤
│  ✅ 表示文件/目录的"路径名"      │
│  ✅ 操作元数据:创建、删除、重命名│
│  ✅ 判断属性:是否存在、是文件还是目录│
│  ❌ 不负责读写文件内容!          │
│     (读写内容 → 交给 IO 流)    │
└─────────────────────────────────┘

2. 路径分隔符:跨平台第一坑 ⚠️

系统 分隔符 错误示例 正确写法
Windows \ new File("D:\test\a.txt") new File("D:/test/a.txt")
Linux/macOS / new File("/home/user/test") ✅ 原生支持
通用方案 File.separator - new File("data" + File.separator + "a.txt")

💡 最佳实践

  • 代码中优先使用 /,Java 在 Windows 上也能自动识别
  • 动态拼接路径时,使用 File.separatorPaths.get()(NIO)

二、File 构造方法:三种姿势,推荐一种

java 复制代码
// ❌ 姿势1:直接传完整路径(硬编码,不灵活)
File file1 = new File("D:/workspace/project/data/a.txt");

// ✅ 姿势2:父路径 + 子路径(最推荐!解耦、易维护)
File dir = new File("data");
File file2 = new File(dir, "a.txt");  // → data/a.txt

// ✅ 姿势3:字符串数组(适合动态拼接多级路径)
File file3 = new File("data", "sub", "a.txt");  // → data/sub/a.txt

🔍 为什么推荐姿势2?

  • 路径可配置:dir 可从 application.yml 读取
  • 便于单元测试:轻松替换临时目录
  • 避免路径拼接错误:new File(dir, name) 自动处理分隔符

三、核心判断方法(必背清单 ✅)

java 复制代码
File file = new File("data/a.txt");

// 🔍 存在性与类型判断
file.exists();           // [关键] 文件/目录是否存在?
file.isFile();           // 是否是普通文件?
file.isDirectory();      // 是否是目录?

// 📋 基本信息获取
file.getName();          // "a.txt"(最后一部分)
file.getPath();          // "data/a.txt"(构造时的路径)
file.getAbsolutePath();  // "/Users/xxx/project/data/a.txt"(完整绝对路径)
file.length();           // 文件大小(字节),目录返回 0
file.lastModified();     // 最后修改时间戳(毫秒)

// 🔄 转换(后续与 NIO 互操作有用)
file.toURI();            // 转为 URI
file.toPath();           // 转为 Path(Java 7+ NIO)

⚠️ 高频陷阱

java 复制代码
File file = new File("a.txt");
System.out.println(file.length()); // 如果文件不存在,返回 0!不是异常!
// ✅ 正确做法:先判断 exists() && isFile()

四、创建 & 删除:安全操作三板斧

1. 创建文件(注意异常处理)

java 复制代码
File file = new File("data/a.txt");

// ✅ 标准写法:先判断 + 捕获异常
if (!file.exists()) {
    // 确保父目录存在!否则 createNewFile 会静默失败
    File parent = file.getParentFile();
    if (parent != null && !parent.exists()) {
        parent.mkdirs(); // 创建父目录
    }
    
    try {
        boolean created = file.createNewFile(); // 原子操作,线程安全
        System.out.println("创建成功:" + created);
    } catch (IOException e) {
        e.printStackTrace(); // 生产环境建议用日志框架
    }
}

2. 创建目录:mkdir() vs mkdirs()

java 复制代码
File dir1 = new File("single");
dir1.mkdir();    // 仅创建单层,父目录不存在则失败

File dir2 = new File("a/b/c");
dir2.mkdirs();   // ✅ 递归创建多级目录(推荐!永远优先用这个)

3. 删除操作:注意"空目录"限制

java 复制代码
// 删除文件
file.delete();  // 成功返回 true

// 删除目录:必须是空目录!
File emptyDir = new File("data/empty");
emptyDir.delete();  // ✅ 成功

// ❌ 非空目录直接 delete() 会失败!需要递归删除(见下方实战任务)

💡 SpringBoot 实践

文件上传时,先检查存储目录是否存在,不存在则 mkdirs() 初始化:

java 复制代码
@Value("${file.upload-dir}")
private String uploadDir;

@PostConstruct
public void init() {
    File dir = new File(uploadDir);
    if (!dir.exists()) {
        dir.mkdirs(); // 应用启动时自动创建上传目录
    }
}

五、遍历文件夹:listFiles() 的正确打开方式

java 复制代码
File dir = new File("data");

// ⚠️ 关键:listFiles() 可能返回 null!(目录不存在/无权限)
File[] files = dir.listFiles();
if (files == null) {
    System.err.println("无法访问目录:" + dir.getAbsolutePath());
    return;
}

// 遍历打印
for (File f : files) {
    String type = f.isDirectory() ? "[DIR] " : "[FILE]";
    System.out.println(type + f.getName() + " (" + f.length() + " bytes)");
}

🔥 进阶:文件名过滤器(只查 .txt 文件)

java 复制代码
// 方式1:匿名内部类
File[] txtFiles = dir.listFiles(new FilenameFilter() {
    @Override
    public boolean accept(File dir, String name) {
        return name.endsWith(".txt");
    }
});

// 方式2:Lambda 表达式(Java 8+,更简洁)
File[] txtFiles = dir.listFiles((d, name) -> name.endsWith(".txt"));

// 方式3:File 过滤器(可结合文件属性)
File[] largeFiles = dir.listFiles(file -> file.isFile() && file.length() > 1024 * 1024);

六、递归遍历:文件扫描的万能钥匙 🔑

java 复制代码
/**
 * 递归打印目录下所有文件(含子目录)
 * @param dir 起始目录
 * @param indent 缩进层级(用于可视化树形结构)
 */
public static void listAll(File dir, String indent) {
    // 1. 参数校验(防御式编程)
    if (dir == null || !dir.exists() || !dir.isDirectory()) {
        return;
    }
    
    // 2. 获取子项(注意 null 检查!)
    File[] files = dir.listFiles();
    if (files == null) return;
    
    // 3. 遍历处理
    for (File f : files) {
        System.out.println(indent + "├─ " + f.getName() + 
                          (f.isDirectory() ? "/" : "") + 
                          (f.isFile() ? " (" + f.length() + "B)" : ""));
        
        // 4. 递归:如果是目录,深入一层
        if (f.isDirectory()) {
            listAll(f, indent + "│  "); // 缩进增加
        }
    }
}

// 调用示例
listAll(new File("data"), "");

输出效果

复制代码
├─ config/
│  ├─ application.yml (2.1KB)
│  └─ logback.xml (1.5KB)
├─ upload/
│  ├─ image.png (3.2MB)
│  └─ doc.pdf (1.1MB)
└─ readme.md (4.2KB)

💡 递归三要素(面试必问):

  1. 终止条件!dir.isDirectory()files == null
  2. 单层逻辑:遍历当前目录所有文件/子目录
  3. 递归调用:遇到子目录时,调用自身处理

七、🎯 今日实战任务:文件管理小工具

任务1:初始化项目目录结构

java 复制代码
// 要求:在当前项目下创建多层目录 data/upload/{image, doc, temp}
// 提示:使用 mkdirs() + 数组遍历
String[] subDirs = {"image", "doc", "temp"};
File baseDir = new File("data/upload");
// TODO: 补全代码...

任务2:实现"安全删除"工具方法

java 复制代码
/**
 * 递归删除文件或目录(无论是否为空)
 * @param file 要删除的文件/目录
 * @return 是否全部删除成功
 */
public static boolean deleteRecursively(File file) {
    // TODO: 实现逻辑
    // 提示:
    // 1. 如果是目录,先递归删除所有子项
    // 2. 再删除自身
    // 3. 注意异常处理和返回值
}

任务3:统计目录信息(综合练习)

java 复制代码
/**
 * 统计目录下:文件总数、总大小、最大文件
 */
public static class DirStat {
    int fileCount;
    long totalSize;
    File largestFile;
    // TODO: 添加构造方法/getter
}

public static DirStat analyzeDir(File dir) {
    // TODO: 递归遍历 + 数据统计
    // 挑战:跳过隐藏文件(file.isHidden())
}

任务4:结合 SpringBoot 配置

yaml 复制代码
# application.yml
app:
  upload:
    base-dir: ./uploads
    allowed-extensions: [jpg, png, pdf]
java 复制代码
// 要求:读取配置,初始化目录,并实现文件扩展名过滤的 listFiles
@Value("${app.upload.allowed-extensions}")
private List<String> allowedExts;

private boolean isAllowed(String filename) {
    // TODO: 判断文件名后缀是否在允许列表中(忽略大小写)
}

📝 第13天 · 核心总结

  1. File 类定位

    • 操作路径/元数据,不读写内容
    • 所有流(FileInputStream 等)的"入口"
  2. 路径兼容性

    • 优先用 /File.separator
    • 构造推荐:new File(parent, child)
  3. 安全操作守则

    • 创建文件前:getParentFile().mkdirs()
    • 创建目录:永远用 mkdirs()
    • 遍历目录:listFiles() 必须判 null
    • 删除目录:非空需递归
  4. 递归遍历模板(背下来!):

    java 复制代码
    if (dir == null || !dir.isDirectory()) return;
    File[] files = dir.listFiles();
    if (files == null) return;
    for (File f : files) {
        // 处理当前文件
        if (f.isDirectory()) listAll(f, indent + "  "); // 递归
    }
  5. SpringBoot 实践点

    • 应用启动时初始化上传/日志目录
    • 配置文件管理路径,避免硬编码
    • 文件上传前校验扩展名、大小

相关推荐
Lyyaoo.2 小时前
Spring Boot自动配置
java·spring boot·后端
不会写DN2 小时前
如何使用PHP创建图像验证码
android·开发语言·php
禾小西2 小时前
深入理解 Java String:从底层原理到高性能优化实战
java·开发语言·性能优化
渔民小镇2 小时前
不用前端也能测试 —— 模拟客户端请求模块详解
java·服务器·前端·分布式·游戏
Huangjin007_2 小时前
【C++类和对象(四)】手撕 Date 类:赋值运算符重载 + 日期计算
开发语言·c++
飞Link2 小时前
深入挖掘 LangChain Community 核心组件,从数据接入到企业级 RAG 实战
开发语言·python·langchain
@atweiwei2 小时前
基于Go语言构建轻量级微服务框架的设计与实现
开发语言·微服务·golang
长不大的小Tom2 小时前
快速学习 C/C++ 并进阶的路线
开发语言·c++