【Java 基础】IO 流 全面详解

1. File 类

1.1 File 类概述

File类是 Java IO 体系中用于表示文件或目录路径的核心类。

主要功能包括:

  • 创建、删除文件 / 目录
  • 判断文件 / 目录是否存在
  • 获取文件 / 目录的路径、名称、大小等信息
  • 注意:File不能读写文件内容,仅处理路径相关操作

1.2 相对路径和绝对路径

  • 绝对路径 :包含盘符(Windows)或根目录(Linux/Mac)的完整路径,例如:
    • Windows:E:/io/java.txt
    • Linux/Mac:/home/user/io/java.txt
  • 相对路径 :相对于当前项目根目录的路径,无需写盘符,例如:io/file(等价于项目根目录下的io/file文件夹)

1.3 构造方法案例

java

运行

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

public class FileTest {
    public static void main(String[] args) throws IOException {
        System.out.println("----------------File类的构造方法-------------------");
        
        // 1. 直接传入绝对路径
        File f1 = new File("E:/io/java.txt");
        System.out.println(f1.getAbsolutePath());// 输出:E:\io\java.txt
        
        // 2. 传入相对路径
        File f2 = new File("io/file");
        System.out.println(f2.getAbsolutePath());// 输出:项目根目录\io\file
        
        // 3. 父路径(字符串)+ 子路径(字符串)
        File f3 = new File("E:/io", "java.txt");
        System.out.println(f3.getAbsolutePath());// 输出:E:\io\java.txt
        
        // 4. 父路径(File对象)+ 子路径(字符串)
        File f4 = new File(new File("E:/IO"), "java.txt");
        System.out.println(f4.getAbsolutePath());// 输出:E:\IO\java.txt
    }
}

1.4 创建功能

File类提供mkdir()(创建单级目录)、mkdirs()(创建多级目录)、createNewFile()(创建文件)方法,代码如下:

java

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

public class FileMethodTest1 {
    public static void main(String[] args) throws IOException {
        System.out.println("-----------创建多级目录----------------");
        File file = new File("io/myFile");
        // System.out.println(file.mkdir());// 只能创建单级目录,若父目录不存在则失败
        System.out.println(file.mkdirs());// 创建多级目录,父目录不存在则自动创建(推荐)
        
        System.out.println("-----------创建文件---------");
        File file1 = new File("io/myFile/java.txt");
        System.out.println(file1.createNewFile());// 若文件不存在则创建,返回true;否则返回false
    }
}

1.5 判断和获取功能

常用判断方法(isDirectory()isFile()exists())和获取方法(getAbsolutePath()getName()等)实战:

java

复制代码
import java.io.File;

public class FileMethodTest2 {
    public static void main(String[] args) {
        File file = new File("io/myFile/java.txt");
        
        System.out.println("------------判断功能-------------");
        System.out.println(file.isDirectory());// 判断是否为目录:false
        System.out.println(file.isFile());// 判断是否为文件:true
        System.out.println(file.exists());// 判断是否存在:true
        
        System.out.println("------------获取功能-------------");
        System.out.println(file.getAbsolutePath());// 获取绝对路径
        System.out.println(file.getPath());// 获取构造方法中传入的路径
        System.out.println(file.getName());// 获取文件/目录名:java.txt
        System.out.println(file.length());// 获取文件大小(字节数)
        
        System.out.println("------------获取当前目录下的文件、目录的名字-------------");
        String[] strArr = new File("D:/").list();// 获取目录下所有文件/目录的名称数组
        for (String str : strArr) {
            System.out.println(str);
        }
        
        System.out.println("------------获取当前目录下的文件、目录的File[]-------------");
        File[] fileArr = new File("D:/").listFiles();// 获取目录下所有文件/目录的File对象数组
        for (File f : fileArr) {
            if(f.isFile()){
                System.out.println(f.getAbsolutePath()+"--->"+f.length());// 文件:输出路径+大小
            }else{
                System.out.println(f.getAbsolutePath());// 目录:仅输出路径
            }
        }
    }
}

1.6 删除功能

delete()方法可删除文件或空目录(非空目录需先删除子内容):

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

public class FileMethodTest3 {
    public static void main(String[] args) throws IOException {
        System.out.println("----------删除文件---------------");
        File file = new File("io/test.txt");
        file.createNewFile();// 先创建文件
        System.out.println(file.delete());// 删除文件:true
        
        System.out.println("----------删除目录---------------");
        File file1 = new File("io/dir");
        file1.mkdirs();// 先创建空目录
        System.out.println(file1.delete());// 删除空目录:true
    }
}

1.7 练习

练习 1:查找指定目录下后缀为.jpg 的文件
复制代码
import java.io.File;

public class JpgFileSearch {
    public static void main(String[] args) {
        File dir = new File("D:/");// 指定目录
        searchJpgFiles(dir);
    }
    
    public static void searchJpgFiles(File dir) {
        if (dir.exists() && dir.isDirectory()) {
            File[] fileArr = dir.listFiles();
            if (fileArr != null) {
                for (File file : fileArr) {
                    if (file.isFile() && file.getName().endsWith(".jpg")) {
                        System.out.println("找到.jpg文件:" + file.getAbsolutePath());
                    } else if (file.isDirectory()) {
                        searchJpgFiles(file);// 递归查找子目录
                    }
                }
            }
        } else {
            System.out.println("目录不存在或不是目录");
        }
    }
}
练习 2:递归遍历目录下所有文件(输出绝对路径)
复制代码
import java.io.File;
import java.io.IOException;

public class FileTraversalTest {
    public static void main(String[] args) throws IOException {
        File srcFile = new File("D:/");
        getAllFilePath(srcFile);
    }

    // 递归遍历目录,输出所有文件的绝对路径
    public static void getAllFilePath(File srcFile) {
        File[] fileArray = srcFile.listFiles();
        if (fileArray != null) {
            for (File file : fileArray) {
                if (file.isDirectory()) {
                    getAllFilePath(file);// 目录:递归调用
                } else {
                    System.out.println(file.getAbsolutePath());// 文件:输出绝对路径
                }
            }
        }
    }
}

2. IO 流概述

2.1 什么是 IO 流?

  • IO 流(Input/Output Stream):用于程序与外部设备(文件、网络、键盘等)之间的数据传输
  • 核心作用:读(Input):从外部设备获取数据到程序;写(Output):从程序输出数据到外部设备
  • 注意:输入 / 输出是相对于程序而言的(例:读取文件→输入流;写入文件→输出流)

2.2 IO 流的分类

IO 流主要分为两大派系,再按数据单位细分:

分类维度 具体类型 核心类(字节流) 核心类(字符流)
数据单位 字节流(8 位) InputStream/OutputStream -
字符流(16 位,char) - Reader/Writer
流向(相对程序) 输入流 FileInputStream FileReader
输出流 FileOutputStream FileWriter
  • 字节流:处理所有类型数据(图片、音频、视频、文本等),直接操作二进制
  • 字符流:仅处理文本数据(.txt、.java 等),自动处理字符编码(UTF-8、GBK)

3. 字节流

字节流是 IO 流的基础,以字节为单位读写数据,适用于所有文件类型。

核心类:FileInputStream(读)、FileOutputStream(写)。

3.1 FileOutputStream(字节输出流)

用于向文件写入字节数据,构造方法支持 "覆盖写入" 和 "追加写入":

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

public class FileOutputStreamTest {
    public static void main(String[] args) throws IOException {
        /**
         * 构造方法说明:
         * 1. FileOutputStream(file):覆盖写入(文件存在则清空原有内容)
         * 2. FileOutputStream(file, true):追加写入(文件存在则在末尾添加)
         */
        File file = new File("fos.txt");
        // 确保父目录存在
        if(!file.exists()){
            file.getParentFile().mkdirs();
            file.createNewFile();
        }
        
        // 追加写入模式
        FileOutputStream fos = new FileOutputStream("fos.txt", true);
        
        // 写入数据(字符串需转字节数组)
        for (int i = 0; i < 10; i++) {
            fos.write("hello FileOutputStream".getBytes());// 写入字符串
            fos.write("\n".getBytes());// 换行
        }
        
        fos.flush();// 刷新缓冲区(强制写入文件)
        fos.close();// 关闭流(必须关闭,释放资源)
    }
}

3.2 FileInputStream(字节输入流)

用于从文件读取字节数据,常用read()方法(返回单个字节,-1 表示读取结束):

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

public class FileInputStreamTest {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            // 创建字节输入流,关联目标文件
            fis = new FileInputStream("fos.txt");
            
            int by;
            // 循环读取:read()返回-1表示文件末尾
            while ((by = fis.read()) != -1) {
                // 字节转字符(适用于文本文件)
                System.out.print((char) by);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 确保流关闭(finally块中执行)
            try {
                if (fis != null) fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

4. 字符流

字符流是字节流的高层封装,以字符为单位读写,自动处理编码,适用于文本文件。

核心类:FileReader(读)、FileWriter(写)。

4.1 FileReader(字符输入流)

读取文本文件的字符数据,read()返回字符的 Unicode 码(-1 表示结束):

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

public class FileReaderTest {
    public static void main(String[] args) throws IOException {
        // 关联文本文件
        FileReader fr = new FileReader("abc.txt");
        
        // 读取单个字符
        int read = fr.read();// 返回Unicode码
        System.out.println((char) read);// 转字符输出
        System.out.println((char) fr.read());// 读取下一个字符
        System.out.println(fr.read());// 无数据时返回-1
        
        fr.close();// 关闭流
    }
}

4.2 FileWriter(字符输出流)

向文本文件写入字符数据,支持直接写入字符串:

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

public class FileWriterTest {
    public static void main(String[] args) throws IOException {
        // 关联文本文件(默认覆盖写入,追加模式:new FileWriter("abc.txt", true))
        FileWriter fw = new FileWriter("abc.txt");
        
        // 直接写入字符串(无需转字节)
        fw.write("快乐、进取、奉献");
        
        fw.flush();// 刷新缓冲区
        fw.close();// 关闭流(关闭前会自动刷新)
    }
}

5. 缓冲流

5.1 为什么使用缓冲流?

普通字节流 / 字符流每次读写都直接操作磁盘,效率低。

缓冲流在内存中创建缓冲区(字节数组 / 字符数组),先将数据缓存到内存,批量读写磁盘,大幅提升效率。

缓冲流是 "装饰器模式" 的应用,需依赖普通流(如FileInputStream)创建。

5.2 BufferedOutputStream(缓冲字节输出流)

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

public class BufferedOutputStreamTest {
    public static void main(String[] args) {
        BufferedOutputStream bos = null;
        try {
            // 底层依赖FileOutputStream,缓冲区大小16字节(默认8192字节)
            bos = new BufferedOutputStream(
                      new FileOutputStream("bos.txt"), 16);
            
            String str = "hello BufferedOutputStream";
            bos.write(str.getBytes());
            
            // bos.flush();// 手动刷新(close()会自动刷新)
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭缓冲流(会自动关闭底层的FileOutputStream)
            try {
                if (bos != null) bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

5.3 BufferedInputStream(缓冲字节输入流)

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

public class BufferedInputStreamTest {
    public static void main(String[] args) {
        BufferedInputStream bis = null;
        try {
            // 底层依赖FileInputStream
            bis = new BufferedInputStream(
                     new FileInputStream("bos.txt"), 16);
            
            byte[] bys = new byte[1024];// 字节数组缓存
            int len;
            // 批量读取(每次读1024字节到bys数组)
            while ((len = bis.read(bys)) != -1) {
                System.out.print(new String(bys, 0, len));// 输出实际读取的字节
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bis != null) bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

5.4 BufferedReader(缓冲字符输入流)

新增readLine()方法,支持按行读取文本,效率极高:

复制代码
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class BufferedReaderTest {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("abc.txt");
        BufferedReader br = new BufferedReader(fr);
        
        String result;
        // 按行读取(返回null表示文件末尾)
        while ((result = br.readLine()) != null) {
            System.out.println(result);
        }
        
        // 关闭流原则:先开后关,后开先关
        fr.close();
        br.close();
    }
}

5.5 BufferedWriter(缓冲字符输出流)

支持newLine()方法(跨平台换行),比\n更通用:

复制代码
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedWriterTest {
    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("abc.txt");
        BufferedWriter bw = new BufferedWriter(fw);
        
        bw.write("学历、能力、素养的三大优势");
        bw.newLine();// 跨平台换行(Windows:\r\n,Linux:\n)
        bw.write("缓冲流提升读写效率");
        
        bw.flush();// 刷新缓冲区
        // 关闭流(先关缓冲流,再关普通流)
        bw.close();
        fw.close();
    }
}

6. 转换流

字符流默认使用系统编码,无法手动指定。

转换流是字节流与字符流的桥梁,支持自定义编码格式(如 UTF-8、GBK),解决中文乱码问题。

核心类:OutputStreamWriter(字节输出流→字符输出流)、InputStreamReader(字节输入流→字符输入流)。

6.1 OutputStreamWriter(字节转字符输出流)

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

public class OutputStreamWriterTest {
    public static void main(String[] args) throws IOException {
        // 1. 创建字节输出流
        FileOutputStream fos = new FileOutputStream("abc.txt");
        
        // 2. 转换为字符流,指定编码为GBK(需与读取时编码一致)
        OutputStreamWriter osw = new OutputStreamWriter(fos, "GBK");
        
        // 3. 写入字符串(按指定编码转换为字节)
        osw.write("文化启迪智慧,教育点亮人生");
        System.out.println(osw.getEncoding());// 输出编码:GBK
        
        // 4. 关闭流
        osw.close();
        fos.close();
    }
}

6.2 InputStreamReader(字节转字符输入流)

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

public class InputStreamReaderTest {
    public static void main(String[] args) throws IOException {
        // 1. 创建字节输入流
        FileInputStream fis = new FileInputStream("abc.txt");
        
        // 2. 转换为字符流,指定编码为GBK(与写入时一致,避免乱码)
        InputStreamReader isr = new InputStreamReader(fis, "GBK");
        
        // 3. 读取数据
        char[] chs = new char[1024];
        int len;
        while ((len = isr.read(chs)) != -1) {
            System.out.print(new String(chs, 0, len));// 输出:文化启迪智慧,教育点亮人生
        }
        
        // 4. 关闭流
        isr.close();
        fis.close();
    }
}

7. 对象流

7.1 概述

对象流用于对象的序列化与反序列化

  • 序列化:将 Java 对象转换为字节序列(可存储到文件、传输网络)
  • 反序列化:将字节序列恢复为 Java 对象

核心类:ObjectOutputStream(序列化)、ObjectInputStream(反序列化)。

7.2 序列化(ObjectOutputStream)

注意 :对象要序列化,其所属类必须实现Serializable接口(标记接口,无抽象方法)。

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

// 实现Serializable接口,支持序列化
class Student implements Serializable {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // getter/setter/toString省略
    @Override
    public String toString() {
        return "Student{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
}

public class ObjectOutputStreamTest {
    public static void main(String[] args) throws IOException {
        // 1. 创建字节输出流
        FileOutputStream fos = new FileOutputStream("io/myObjectStream/oos.txt");
        
        // 2. 创建对象输出流(序列化流)
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        
        // 3. 序列化对象(写入文件)
        Student s = new Student("林青霞", 30);
        oos.writeObject(s);
        
        // 4. 关闭流
        oos.close();
    }
}

7.3 反序列化(ObjectInputStream)

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

public class ObjectInputStreamTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 1. 创建字节输入流
        FileInputStream fis = new FileInputStream("io/myObjectStream/oos.txt");
        
        // 2. 创建对象输入流(反序列化流)
        ObjectInputStream ois = new ObjectInputStream(fis);
        
        // 3. 反序列化对象(读取字节序列→恢复对象)
        Object obj = ois.readObject();
        Student s = (Student) obj;
        
        System.out.println(s);// 输出:Student{name='林青霞', age=30}
        
        // 4. 关闭流
        ois.close();
    }
}

7.4 serialVersionUID(序列化版本号)

serialVersionUID是序列化 / 反序列化的版本标识符,用于确保反序列化时的类版本与序列化时一致:

  • 自动生成:编译器根据类名、方法、字段自动计算(类结构变化时会改变)
  • 手动指定(推荐):在类中添加private static final long serialVersionUID = 1L;,类结构小改时仍可反序列化

7.5 transient(瞬时关键字)

若对象的某个成员变量无需序列化(如敏感数据、临时数据),用transient修饰:

复制代码
class Student implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private transient int age; // age字段不序列化
    
    // 其余代码不变
}

反序列化后,transient字段会恢复为默认值(int→0,String→null)

练习:Account 对象序列化与反序列化

要求:Account 类(aid、accNo、accMoney)实现序列化,accMoney 为敏感数据无需序列化。

java

复制代码
import java.io.*;

class Account implements Serializable {
    private static final long serialVersionUID = 1L;
    private int aid;
    private String accNo;
    private transient double accMoney; // 敏感数据,不序列化

    public Account(int aid, String accNo, double accMoney) {
        this.aid = aid;
        this.accNo = accNo;
        this.accMoney = accMoney;
    }

    @Override
    public String toString() {
        return "Account{" +
                "aid=" + aid +
                ", accNo='" + accNo + '\'' +
                ", accMoney=" + accMoney +
                '}';
    }
}

public class AccountSerializeTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 序列化
        FileOutputStream fos = new FileOutputStream("account.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(new Account(1, "622208XXXXXXXXXXXX", 10000.0));
        oos.close();
        
        // 反序列化
        FileInputStream fis = new FileInputStream("account.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Account account = (Account) ois.readObject();
        System.out.println(account);
        // 输出:Account{aid=1, accNo='622208XXXXXXXXXXXX', accMoney=0.0}(accMoney为默认值)
        ois.close();
    }
}

总结

Java IO 流的核心知识点与使用场景:

  1. File 类:处理文件 / 目录路径,无读写功能,是 IO 操作的基础。
  2. 字节流 :处理所有文件类型,FileInputStream/FileOutputStream是基础。
  3. 字符流 :仅处理文本,自动编码,FileReader/FileWriter简化文本操作。
  4. 缓冲流 :装饰普通流,提升效率,BufferedReaderreadLine()是文本读取首选。
  5. 转换流 :解决编码问题,OutputStreamWriter/InputStreamReader支持自定义编码。
  6. 对象流 :实现对象序列化,需Serializable接口,transient排除敏感字段。

使用建议

  • 文本文件:字符流 / 缓冲字符流(效率高、无乱码)
  • 非文本文件(图片、视频):字节流 / 缓冲字节流
  • 需指定编码:转换流
  • 存储 / 传输对象:对象流

掌握以上内容,即可应对 Java 开发中绝大多数 IO 场景!如果有疑问,欢迎在评论区交流~

相关推荐
源码获取_wx:Fegn08951 小时前
计算机毕业设计|基于springboot + vue网上超市系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·spring·课程设计
码农水水2 小时前
阿里Java面试被问:Online DDL的INSTANT、INPLACE、COPY算法差异
java·服务器·前端·数据库·mysql·算法·面试
Coder_Boy_2 小时前
基于SpringAI的在线考试系统-阅卷评分与错题管理模块回归测试逻辑梳理文档
java·spring boot·系统架构·ddd·tdd·全栈开发
那起舞的日子2 小时前
Java线程池-执行顺序
java
吃吃喝喝小朋友2 小时前
JavaScript事件
开发语言·前端·javascript
先做个垃圾出来………2 小时前
Linux/Unix系统下的基础文本处理命令
java·linux·unix
风若飞2 小时前
Linux 环境下解决 Tomcat8 与 JDK8 配置问题
java·linux·运维·服务器·tomcat
ONExiaobaijs2 小时前
Java jdk运行库合集
java·开发语言·python
wangjialelele2 小时前
二刷C语言后,一万字整理细碎知识点
c语言·开发语言·数据结构·c++·算法·cpp