三十六、File 类与 IO 流基础——文件操作的「第一步」

三十六、File 类与 IO 流基础------文件操作的「第一步」📁

😫 痛点引入 :Java 程序的数据都在内存里,一关机就没了?想操作磁盘上的文件却不知道从何下手?

别慌!今天带你从 File 类 开始,搞定文件路径、创建删除、判断获取,再入门 IO 字节流,让数据在内存和磁盘之间跑起来!🏃


一、File 类------文件和目录的「地图」🗺️

1.1 File 是什么

File 是文件和目录路径名的表示,在 java.io 包里。

关键理解 :File 封装的不是真正存在的文件,只是一个路径。你可以把它理解为「地址牌」------写了个地址,不代表那栋楼已经建好了 🏗️

1.2 路径的两种写法

路径类型 说明 示例
绝对路径 📍 从盘符开始的完整路径 D:\Java\JDK\bin
相对路径 📌 相对于项目根目录的简化路径 a/b/c.txt

⚠️ 注意 :IDEA 中默认的相对路径是当前项目的根目录

1.3 路径分隔符

Windows 路径中的 \ 是转义字符,表示一个确切的 \ 需要写 \\

java 复制代码
// 方式1:双反斜杠(Windows 风格)
File f1 = new File("D:\\Java\\JDK\\bin");

// 方式2:正斜杠(推荐,跨平台通用)
File f2 = new File("D:/Java/JDK/bin");

二、File 构造方法 🏗️

2.1 四种构造方式

构造方法 说明
File(String path) 将路径字符串封装为 File 对象
File(String parent, String child) 将两个路径拼接后封装
File(File parent, String child) 将 File 对象和路径拼接
File(URI uri) 将 URI 封装为 File 对象

2.2 代码示例

java 复制代码
import java.io.File;

public class Demo01 {
    public static void main(String[] args) {
        // 方式1:直接传路径字符串
        File f1 = new File("D:/Java/JDK/bin");
        System.out.println("方式1:" + f1);

        // 方式2:拼接两个路径字符串
        File f2 = new File("D:/Java", "JDK/bin");
        System.out.println("方式2:" + f2);

        // 方式3:File对象 + 子路径
        File f3 = new File(f1, "java");
        System.out.println("方式3:" + f3);
    }
}

三、File 创建与删除 ✂️

3.1 方法速查表

方法 功能 返回值
createNewFile() 创建文件 📄 boolean,成功 true
mkdir() 创建单级文件夹 📁 boolean,成功 true
mkdirs() 创建多级文件夹 📂 boolean,成功 true
delete() 删除文件或空文件夹 🗑️ boolean,成功 true

3.2 ⚠️ 注意事项

  1. mkdir() 只能创建最后一级文件夹,父级路径必须存在
  2. mkdirs() 可以创建多级文件夹,父级不存在会自动创建
  3. delete() 不走回收站!删了就没了!
  4. delete() 只能删除空文件夹(里面不能有任何内容)

3.3 代码示例

java 复制代码
import java.io.File;
import java.io.IOException;

public class Demo02 {
    public static void main(String[] args) throws IOException {
        // 1. 创建文件(绝对路径)
        File f1 = new File("D:/1.txt");
        System.out.println("创建文件:" + f1.createNewFile());  // true
        // 再次创建,文件已存在,返回false
        System.out.println("再次创建:" + f1.createNewFile());  // false

        // 2. 创建单级文件夹(相对路径,相对于项目根目录)
        File f2 = new File("a");
        System.out.println("创建单级文件夹:" + f2.mkdir());  // true

        // 3. 创建多级文件夹
        File f3 = new File("a/b/c/d");
        System.out.println("创建多级文件夹:" + f3.mkdirs());  // true

        // 4. 删除文件(不走回收站!⚠️)
        File f4 = new File("D:/1.txt");
        System.out.println("删除文件:" + f4.delete());  // true

        // 5. 删除文件夹(只能删空文件夹!)
        File f5 = new File("a/b/c/d");
        System.out.println("删除空文件夹:" + f5.delete());  // true
    }
}

四、File 判断与获取 🔍

4.1 判断方法

方法 功能
exists() 路径是否存在
isFile() 是否是文件
isDirectory() 是否是文件夹

4.2 获取方法

方法 功能
getAbsolutePath() 获取绝对路径
getPath() 获取封装的路径
getName() 获取文件/文件夹名称(最后一级)
length() 获取文件大小(字节数)
list() 获取文件夹下所有子项名称 → String[]
listFiles() 获取文件夹下所有子项 File 对象 → File[]

4.3 代码示例

java 复制代码
import java.io.File;

public class Demo03 {
    public static void main(String[] args) {
        File f = new File("a");

        // 判断功能
        System.out.println("路径是否存在:" + f.exists());
        System.out.println("是否是文件:" + f.isFile());
        System.out.println("是否是文件夹:" + f.isDirectory());

        // 获取功能
        System.out.println("绝对路径:" + f.getAbsolutePath());
        System.out.println("封装路径:" + f.getPath());
        System.out.println("名称:" + f.getName());

        // 获取文件夹下的子项
        if (f.isDirectory()) {
            System.out.println("--- list() 获取名称 ---");
            String[] names = f.list();
            for (String name : names) {
                System.out.println(name);
            }

            System.out.println("--- listFiles() 获取File对象 ---");
            File[] files = f.listFiles();
            for (File file : files) {
                System.out.println(file.getAbsolutePath());
            }
        }
    }
}

五、实战:筛选文件夹中的文件 💪

需求 :获取文件夹内以 .txt 为后缀且大小大于 1KB 的文件的绝对路径。

java 复制代码
import java.io.File;
import java.util.Scanner;

public class Demo04 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("请输入文件夹路径:");
        File f = new File(sc.next());

        // 第一步:判断文件夹是否存在
        if (!f.exists()) {
            System.out.println("文件夹不存在!");
            return;
        }

        // 第二步:获取文件夹下所有文件对象
        File[] files = f.listFiles();
        if (files == null) {
            System.out.println("文件夹为空!");
            return;
        }

        // 第三步:筛选 .txt 且 > 1KB 的文件
        int count = 0;
        for (File file : files) {
            if (file.getName().endsWith(".txt") && file.length() > 1024) {
                System.out.println("找到文件:" + file.getAbsolutePath());
                count++;
            }
        }
        System.out.println("共找到 " + count + " 个符合条件的文件");
    }
}

六、IO 流入门------数据传输的「管道」🚰

6.1 什么是 IO 流

约翰·冯·诺依曼 说过:永远站在内存的角度看待问题!

  • 输入流(Input):从外部往内存里进 → 读数据 📥

  • 输出流(Output):从内存往外出 → 写数据 📤

    磁盘文件 ──输入流──→ 内存(Java程序)──输出流──→ 磁盘文件
    (读数据) (写数据)

6.2 IO 流的分类

分类维度 类型 说明
按方向 输入流 / 输出流 读 / 写
按数据 字节流 / 字符流 字节(万能)/ 字符(文本)

6.3 字节流 vs 字符流

对比项 字节流 字符流
操作单位 字节(byte) 字符(char)
后缀标识 Stream Reader / Writer
适用场景 所有文件(万能)✅ 纯文本文件
中文处理 需要编码表 自动处理 ✅
推荐场景 不确定文件类型时优先用 操作文本时优先用

七、字节输入流 FileInputStream 📥

7.1 核心概念

FileInputStreamInputStream 的子类,用于从磁盘文件读取字节数据。

特点

  • 可以读取文本、视频、音频、图片等所有类型的文件
  • 计算机中一切数据都以字节为单位存储

7.2 构造方法

构造方法 说明
FileInputStream(File f) 将 File 对象封装为字节输入流
FileInputStream(String path) 将路径字符串封装为字节输入流

⚠️ 注意 :只能封装文件路径,封装文件夹没有意义(文件夹本身没数据)。

7.3 读取方法

方法 功能 返回值
read() 读取一个字节 int,读到末尾返回 -1
read(byte[] arr) 读取数组大小的字节 int,本次读取的字节个数
close() 关闭流 void

7.4 代码示例

方式一:单字节读取

java 复制代码
import java.io.FileInputStream;
import java.io.IOException;

public class Demo05 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("1.txt");

        int i;  // 记录读取到的字节信息
        while ((i = fis.read()) != -1) {
            System.out.print((char) i);  // 转为字符打印
        }

        fis.close();  // 关闭流!一定要关!
    }
}

方式二:字节数组读取(推荐 ⭐效率更高)

java 复制代码
import java.io.FileInputStream;
import java.io.IOException;

public class Demo06 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("1.txt");

        byte[] b = new byte[4];  // 缓冲数组
        int len;  // 记录本次读取的字节个数

        while ((len = fis.read(b)) != -1) {
            // ⚠️ 一定要用 len 指定转换个数!
            // 最后一次读取可能不够数组大小,不指定会有残留数据
            System.out.print(new String(b, 0, len));
        }

        fis.close();
    }
}

💡 为什么一定要 new String(b, 0, len) 而不是 new String(b)

复制代码
假设文件内容:abcde(5个字节)
数组大小:4

第一次读取:[a, b, c, d]  len=4  → new String(b,0,4) = "abcd" ✅
第二次读取:[e, b, c, d]  len=1  → new String(b,0,1) = "e" ✅
                                    new String(b)     = "ebcd" ❌ 残留数据!

八、字节输出流 FileOutputStream 📤

8.1 构造方法

构造方法 说明
FileOutputStream(File f) 封装为字节输出流(覆盖模式)
FileOutputStream(String path) 封装为字节输出流(覆盖模式)
FileOutputStream(String path, boolean append) append=true 追加模式 🔗

覆盖 vs 追加

  • append=false(默认):每次写入相当于新建文件,原内容没了 ❌
  • append=true:在原文件内容后面拼接

8.2 写出方法

方法 功能
write(int b) 写出一个字节
write(byte[] b) 写出字节数组
write(byte[] b, int off, int len) 写出数组的一部分

8.3 代码示例

java 复制代码
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo07 {
    public static void main(String[] args) throws IOException {
        // 覆盖模式(默认)
        FileOutputStream fos = new FileOutputStream("2.txt");

        // 写出一个字节
        fos.write('a');   // 写出字符a
        fos.write(98);    // 写出数字98(对应字符b)

        // 写出字节数组
        byte[] b = {65, 66, 67, 68};
        fos.write(b);     // 写出ABCD

        // 写出数组的一部分
        fos.write(b, 1, 2);  // 写出BC

        // 写出字符串(先转为字节数组)
        fos.write("书源最帅".getBytes());

        fos.close();
    }
}

追加模式示例

java 复制代码
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo08 {
    public static void main(String[] args) throws IOException {
        // 第二个参数为true → 追加模式,在原文件末尾拼接
        FileOutputStream fos = new FileOutputStream("2.txt", true);

        fos.write("这是追加的内容".getBytes());
        fos.close();
    }
}

九、文件复制------读写结合 📋

核心思路:读取源文件 → 写出到目标文件

java 复制代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo09 {
    public static void main(String[] args) throws IOException {
        // 源文件(读)
        FileInputStream fis = new FileInputStream("1.txt");
        // 目标文件(写)
        FileOutputStream fos = new FileOutputStream("3.txt");

        int i;
        while ((i = fis.read()) != -1) {
            fos.write(i);  // 读取一个字节,写出一个字节
        }

        // 先关输出流,再关输入流
        fos.close();
        fis.close();
    }
}

十、高效字节缓冲流 ⚡

10.1 为什么需要缓冲流?

普通字节流每次读写一个字节,都要访问磁盘,效率极低 🐌

缓冲流底层有一个 8192 字节(8KB) 的缓冲数组,一次性读写一大批数据,减少磁盘交互次数,效率飞升 🚀

复制代码
普通流:  磁盘 → 1字节 → 内存 → 1字节 → 磁盘  (频繁访问磁盘)
缓冲流:  磁盘 → 8192字节 → 缓冲区 → 取1个 → 内存  (批量读取)

10.2 构造方法

构造方法 说明
BufferedInputStream(InputStream is) 包装字节输入流
BufferedOutputStream(OutputStream os) 包装字节输出流

10.3 代码示例

java 复制代码
import java.io.*;

public class Demo10 {
    public static void main(String[] args) throws IOException {
        // 包装普通流,提升效率
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("1.txt"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("4.txt"));

        int i;
        while ((i = bis.read()) != -1) {
            bos.write(i);
        }

        bos.close();  // close() 会自动调用 flush()
        bis.close();
    }
}

10.4 ⚠️ 缓冲输出流的注意点

java 复制代码
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("3.txt"));
bos.write(97);  // 数据还在缓冲区,没有写到磁盘!

// 方式1:手动刷新
bos.flush();    // 强制将缓冲区内容写到磁盘

// 方式2:关闭流时自动刷新
bos.close();    // close() 内部会自动调用 flush()

10.5 性能对比(拷贝视频文件)

java 复制代码
import java.io.*;

public class Demo11 {
    public static void main(String[] args) throws IOException {
        test_缓冲流拷贝();
        test_普通流拷贝();
    }

    // 缓冲流拷贝
    private static void test_缓冲流拷贝() throws IOException {
        long start = System.currentTimeMillis();

        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("1.mp4"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("3.mp4"));

        int i;
        while ((i = bis.read()) != -1) {
            bos.write(i);
        }

        bos.close();
        bis.close();

        long end = System.currentTimeMillis();
        System.out.println("缓冲流拷贝耗时:" + (end - start) + "ms");  // 几十ms
    }

    // 普通流拷贝
    private static void test_普通流拷贝() throws IOException {
        long start = System.currentTimeMillis();

        FileInputStream fis = new FileInputStream("1.mp4");
        FileOutputStream fos = new FileOutputStream("2.mp4");

        int i;
        while ((i = fis.read()) != -1) {
            fos.write(i);
        }

        fos.close();
        fis.close();

        long end = System.currentTimeMillis();
        System.out.println("普通流拷贝耗时:" + (end - start) + "ms");  // 几千ms
    }
}

预期结果 :缓冲流比普通流快 几十倍!⚡


十一、IO 异常处理------流一定要关!🔒

11.1 传统方式(Java 6 及以前)

java 复制代码
import java.io.*;

public class Demo12 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("1.txt");
            int i;
            while ((i = fis.read()) != -1) {
                System.out.print((char) i);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // finally 中关闭流,确保一定会执行
            try {
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

11.2 try-with-resources(Java 7+ ⭐推荐)

java 复制代码
import java.io.*;

public class Demo13 {
    public static void main(String[] args) {
        // 在 try() 中创建流对象,使用完自动关闭!
        try (
            FileInputStream fis = new FileInputStream("1.txt");
            FileOutputStream fos = new FileOutputStream("5.txt");
        ) {
            int i;
            while ((i = fis.read()) != -1) {
                fos.write(i);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 不需要手动 close(),自动关闭!✅
    }
}

11.3 两种方式对比

方式 代码量 是否自动关闭 推荐度
try-finally 多(嵌套 try) 手动关闭 ⭐⭐
try-with-resources 自动关闭 ✅ ⭐⭐⭐⭐⭐

本篇总结 📝

  1. File 类 🗺️:封装路径,创建/删除文件和文件夹
  2. 路径 📍:绝对路径(从盘符开始)、相对路径(相对项目根目录)
  3. IO 流 🚰:输入流(读)、输出流(写),永远站在内存角度
  4. 字节流 📦:FileInputStream/FileOutputStream,万能流,一切皆字节
  5. 缓冲流 ⚡:8KB 缓冲数组,减少磁盘交互,效率翻倍
  6. 文件复制 📋:读一个写一个,先关输出再关输入
  7. 异常处理 🔒:try-with-resources 自动关闭流(Java 7+)

🚀 下一篇预告 :《三十七、字符流与转换流------中文处理的「救星」》------ 用来搞定字符流读写、编码表、转换流,再也不怕中文乱码


作者 :书源丶
发布平台 :CSDN
系列:「Java基础篇」

相关推荐
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题】【Java基础篇】第30题:JDK动态代理和CGLIB动态代理有什么区别
java·开发语言·后端·面试·代理模式
DFT计算杂谈2 小时前
wannier90 参数详解大全
java·前端·css·html·css3
marsh02062 小时前
43 openclaw熔断与降级:保障系统在异常情况下的可用性
java·运维·网络·ai·编程·技术
张健11564096483 小时前
临界区和同一线程上锁
java·开发语言·jvm
超梦dasgg3 小时前
智慧充电系统设备管理服务对外接口实现方案
java·spring·微服务
xiaoye37084 小时前
Spring 事务传播机制 + 隔离级别
java·后端·spring
Arya_aa4 小时前
数据字典模块–JSR303参数校验
java
明月(Alioo)5 小时前
给 AI Agent 装上“大脑“:Java语言中Code Interpreter 的设计与实现
java·ai·agent
QuZero5 小时前
StampedLock Mechanism
java·算法