☕ 第十章 I/O
📝 一句话概括

IO 章开门第一课:先认识"文件"本身,再学搬运数据。File 类是文件和文件夹的"路标"——注意,new File 只是造路标,不是造文件。学完这节,你就能用代码查文件、建文件夹、扫目录。

1. 引入:为什么要学 IO

前面写的程序数据都住在内存里——程序一关,全没了。想让数据"住得久"(保存账号、存日志、读配置),就得落到硬盘上:往文件里写、从文件里读——这就是 I/O(Input/Output,输入/输出)。

但搬数据之前,先得能找到并描述文件。Java 用 java.io.File 类干这件事:

2. 创建 File 对象:三种写法

File f1 = new File("D:\\test\\a.txt");            // 写全路径
File f2 = new File("D:\\test", "a.txt");          // 父路径 + 子路径(字符串拆开)
File f3 = new File(dir, "a.txt");                 // 父路径用 File 对象装

三个要点:

  1. new File ≠ 创建文件——它只是在内存里造了个"路标"。磁盘上有没有这个文件,new 的时候一概不管、也不报错。想真建文件得调 createNewFile()。
  2. 绝对路径 vs 相对路径:D:\test\a.txt 从盘符写起是绝对路径;a.txt、./a.txt 从项目目录找起是相对路径——换台电脑跑,绝对路径可能就失效了,练习时相对路径更稳。
  3. 路径分隔符:Windows 是 \,但 \ 在字符串里是转义符,得写 \\;偷懒写 / 也行(Java 帮你兼容)。
📝 类比 9.1

这套路是不是很熟?File 对象就像 Class 对象的远房亲戚——都是"抽象表示":Class 对象是类的说明书,File 对象是文件的路标。真正的"活儿"(读写数据/操作字节)都要靠后面的 IO 流来干。

3. File 常用方法:四大族

判断族

f.exists()        // 路标指向的文件/文件夹真的存在吗?
f.isFile()        // 是文件吗?
f.isDirectory()   // 是文件夹吗?

获取族

f.getName()          // a.txt(文件名)
f.getAbsolutePath()  // D:\test\a.txt(绝对路径)
f.length()           // 字节数(注意:文件夹的 length 无意义,返回 0 或垃圾值)
f.lastModified()     // 最后修改时间(毫秒值 → 6.6 的 Date 能接住)

创建/删除族

f.createNewFile()   // 真建文件!已存在返回 false;抛受检 IOException
f.mkdirs()          // 真建文件夹!多级一起建(a/b/c 一条龙),mkdir() 只建一级
f.delete()          // 直接删除,不进回收站!文件夹必须为空才能删

遍历族(本节重头戏)

String[] names = dir.list();          // 只给名字(字符串数组)
File[] files = dir.listFiles();       // 给 File 对象数组——能接着调方法,实用
File[] txts  = dir.listFiles(过滤器);  // 想挑着拿?传过滤器

过滤器是函数式接口 FileFilter(只有一个 accept(File f) 方法)——4.7 的 Lambda 直接上岗:

File[] txts = dir.listFiles(f -> f.getName().endsWith(".txt") && f.isFile());

4. 收网旧账

5. 动手:Demo05 给文件做个体检 + 扫目录

import java.io.File;
import java.io.IOException;

public class Demo05 {
    public static void main(String[] args) throws IOException {
        // 一、体检一个文件
        File f = new File("test\\a.txt");
        System.out.println("存在吗:" + f.exists());
        System.out.println("绝对路径:" + f.getAbsolutePath());

        if (!f.exists()) {
            System.out.println("创建成功吗:" + f.createNewFile());  // 真建文件
        }
        System.out.println("是文件吗:" + f.isFile() + ",大小:" + f.length());

        // 二、建个多级文件夹
        File dir = new File("test\\demo\\src");
        System.out.println("建文件夹:" + dir.mkdirs());

        // 三、扫目录:只挑 .txt(Lambda 过滤器)
        File[] files = new File("test").listFiles(fd -> fd.getName().endsWith(".txt"));
        for (File file : files) {
            System.out.println(file.getName() + " → " + file.length() + " 字节");
        }
    }
}

三个 30 秒实验:

  1. 把 new File("test\\a.txt") 改成 new File("test/a.txt") 再跑——照常成功:/ 也能当分隔符。
  2. 对 a.txt(文件,不是文件夹)调 listFiles()——返回 null,接着 .length 直接 NPE。亲手踩一遍坑。
  3. 给 a.txt 调 delete() 后去 D 盘翻回收站——没有!它被直接删了(所以 delete 前先 exists() 三思)。

6. 常见误区

⚠️ 五个坑
  1. ❌ 以为 new File 会创建文件 —— 只是造路标。不调 createNewFile() 磁盘上啥也没有。
  2. ❌ delete() 期待走回收站 —— 直接送走,找不回来!删重要东西前先确认路径(这也是我们删文件永远先备份的原因)。
  3. ❌ 对不存在的路径 / 文件路径调 listFiles() —— 返回 null,接一个 for 循环直接 NPE。先 exists() + isDirectory() 再遍历。
  4. ❌ mkdir() 想一级建多级 —— a/b/c 中间哪级不存在就失败返回 false;用 mkdirs() 一条龙。
  5. ❌ 对文件夹调 length() —— 文件夹没有"大小"这个概念,结果不可信;要算大小得遍历里面所有文件累加(后面的递归活儿)。

7. 可折叠自测

❓ Q1:`new File("D:\\test\\a.txt")` 执行后,磁盘上有了 a.txt 吗?

没有!只是内存里多了个路标对象。磁盘有没有这个文件与它无关,用 exists() 查,用 createNewFile() 才真建。

❓ Q2:`File` 既表示文件又表示文件夹,用什么方法区分它指向的是哪一种?

isFile() / isDirectory()。另外 exists() 永远是第一步——路标可能指向空地。

❓ Q3:`mkdir()` 和 `mkdirs()` 的区别?建 `a/b/c` 三级该用哪个?

mkdir() 只建最后一级且父目录必须已存在;mkdirs() 把缺的父目录全建齐。建多级用 mkdirs()。

❓ Q4:`list()` 和 `listFiles()` 返回类型差在哪?实战选哪个?

list() 返回 String[](只有名字);listFiles() 返回 File[](每个元素还能继续调方法)。实战基本都用 listFiles()。

❓ Q5(毕业题):不看笔记,用一行 Lambda 过滤器 + 遍历,打印某目录下所有 `.md` 文件的名字和字节数,并在开头判断"该目录存在且真是文件夹"。
File dir = new File("笔记目录");
if (dir.exists() && dir.isDirectory()) {
    File[] mds = dir.listFiles(f -> f.isFile() && f.getName().endsWith(".md"));
    for (File f : mds) System.out.println(f.getName() + " → " + f.length() + " 字节");
}

写出来,你已经能用代码"逛文件夹"了——下节开始往文件里装数据。

8. 下一步学什么

相关笔记