Java IO 基础理论知识笔记

一、Java IO 概述

Java IO(Input/Output,输入 / 输出)是 Java 语言用于实现程序与外部设备(如文件、网络、控制台等)之间数据传输的核心技术。它是 Java 程序与外界交互的重要桥梁,比如读取本地配置文件、向磁盘写入日志、通过网络发送数据等场景,都离不开 Java IO 的支持。

Java IO 体系的设计基于流(Stream) 的概念,流是一个抽象的概念,代表了数据的流动方向。根据数据的传输方向,可将流分为输入流和输出流:

  • 输入流(Input Stream):数据从外部设备流向程序,程序读取数据,例如读取文件中的内容到内存。
  • 输出流(Output Stream):数据从程序流向外部设备,程序写入数据,例如将内存中的数据写入到文件。

Java IO 体系主要包含两大核心部分:字节流字符流,此外还提供了缓冲流、转换流、对象流等功能扩展流,以满足不同的业务需求。

二、字节流(Byte Stream)

字节流是 Java IO 的基础,它以字节(Byte) 为基本单位处理数据,一个字节占 8 位(bit)。字节流可以处理任意类型的文件,包括文本文件、图片、音频、视频等二进制文件,是 Java IO 中最通用的流类型。

字节流的顶层是两个抽象类:InputStream(字节输入流)和 OutputStream(字节输出流),所有的字节输入流都继承自 InputStream,所有的字节输出流都继承自 OutputStream

1. 字节输入流(InputStream)核心方法

InputStream 是所有字节输入流的父类,定义了字节输入流的通用操作,核心方法如下:

  • int read():从输入流中读取一个字节的数据,返回读取的字节值(0-255);如果到达流的末尾,返回 -1
  • int read(byte[] b):从输入流中读取一定长度的字节,存储到字节数组 b 中,返回实际读取的字节数;如果到达流的末尾,返回 -1
  • int read(byte[] b, int off, int len):从输入流中读取长度为 len 的字节,从字节数组 b 的下标 off 开始存储,返回实际读取的字节数。
  • void close():关闭输入流,释放相关的系统资源。注意:流使用完毕后必须关闭,否则会造成资源泄漏。

2. 字节输出流(OutputStream)核心方法

OutputStream 是所有字节输出流的父类,核心方法如下:

  • void write(int b):将一个字节的数据写入输出流(注意:参数是 int 类型,但实际只写入低 8 位)。
  • void write(byte[] b):将字节数组 b 中的所有字节写入输出流。
  • void write(byte[] b, int off, int len):将字节数组 b 中从下标 off 开始、长度为 len 的字节写入输出流。
  • void flush():刷新输出流,将缓冲区中的数据强制写入目标设备。对于带缓冲区的输出流,此方法尤为重要。
  • void close():关闭输出流,释放系统资源。关闭前会自动调用 flush() 方法。

3. 常用字节流实现类

(1)文件字节流:FileInputStream & FileOutputStream

这是最常用的字节流实现类,用于操作本地文件,直接与文件系统交互。

  • FileInputStream:从本地文件中读取字节数据。构造方法:
java 复制代码
// 通过文件路径创建流
FileInputStream fis = new FileInputStream("test.txt");
// 通过 File 对象创建流
File file = new File("test.txt");
FileInputStream fis = new FileInputStream(file);

FileOutputStream :向本地文件中写入字节数据。注意:如果文件不存在,会自动创建;如果文件已存在,默认会覆盖原有内容,也可以通过构造方法指定追加模式。构造方法:

java 复制代码
// 覆盖模式
FileOutputStream fos = new FileOutputStream("test.txt");
// 追加模式
FileOutputStream fos = new FileOutputStream("test.txt", true);

使用示例:文件复制

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

public class FileCopyDemo {
    public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            // 1. 创建输入流和输出流
            fis = new FileInputStream("source.jpg");
            fos = new FileOutputStream("target.jpg");
            // 2. 定义缓冲区,提高读写效率
            byte[] buffer = new byte[1024];
            int len;
            // 3. 循环读取并写入
            while ((len = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
            }
            System.out.println("文件复制成功");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4. 关闭流,先关输出流,再关输入流
            try {
                if (fos != null) fos.close();
                if (fis != null) fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
(2)字节数组流:ByteArrayInputStream & ByteArrayOutputStream

字节数组流以内存中的字节数组为操作对象,数据的读取和写入都在内存中完成,不会涉及磁盘 IO,常用于临时数据的处理。

  • ByteArrayInputStream:从字节数组中读取数据。
  • ByteArrayOutputStream:向内存中的字节数组写入数据,其内部会自动扩容,可通过 toByteArray() 方法获取写入的字节数组。
java 复制代码
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class ByteArrayStreamDemo {
    public static void main(String[] args) throws IOException {
        String content = "Hello, Java IO!";
        byte[] bytes = content.getBytes();
        
        // 字节数组输入流
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        // 字节数组输出流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        
        int len;
        byte[] buffer = new byte[10];
        while ((len = bais.read(buffer)) != -1) {
            baos.write(buffer, 0, len);
        }
        
        // 获取写入的字节数组并转换为字符串
        String result = baos.toString();
        System.out.println(result); // 输出:Hello, Java IO!
        
        // 关闭流(字节数组流关闭后不影响内存数据,可省略,但建议养成习惯)
        bais.close();
        baos.close();
    }
}

三、字符流(Character Stream)

字节流以字节为单位处理数据,在处理文本文件时,需要考虑字符编码(如 UTF-8、GBK),否则容易出现乱码问题。字符流正是为了解决这一问题而设计的,它以字符(Character) 为基本单位处理数据,一个字符通常占 2 个字节(Java 中 char 类型是 16 位),并且会自动处理字符编码问题。

字符流的顶层是两个抽象类:Reader(字符输入流)和 Writer(字符输出流),所有字符输入流都继承自 Reader,所有字符输出流都继承自 Writer

1. 字符输入流(Reader)核心方法

  • int read():读取一个字符,返回字符的 Unicode 编码值;如果到达流的末尾,返回 -1
  • int read(char[] cbuf):读取若干字符到字符数组 cbuf 中,返回实际读取的字符数;末尾返回 -1
  • int read(char[] cbuf, int off, int len):读取长度为 len 的字符,从字符数组 cbuf 的下标 off 开始存储。
  • void close():关闭字符输入流,释放资源。

2. 字符输出流(Writer)核心方法

  • void write(int c):写入一个字符(参数是 int 类型,实际写入低 16 位)。
  • void write(char[] cbuf):写入字符数组 cbuf 中的所有字符。
  • void write(char[] cbuf, int off, int len):写入字符数组 cbuf 中从 off 开始、长度为 len 的字符。
  • void write(String str):直接写入一个字符串,这是字符流特有的便捷方法。
  • void write(String str, int off, int len):写入字符串 str 中从 off 开始、长度为 len 的子串。
  • void flush():刷新缓冲区,将数据写入目标设备。
  • void close():关闭输出流,关闭前自动刷新。

3. 常用字符流实现类

(1)文件字符流:FileReader & FileWriter

用于操作本地文本文件,底层会默认使用系统的字符编码,也可以通过指定编码来避免乱码(JDK 11 及以上支持通过构造方法指定编码)。

  • FileReader:读取文本文件内容。构造方法:
java 复制代码
FileReader fr = new FileReader("test.txt");
// 指定编码(JDK 11+)
FileReader fr = new FileReader("test.txt", Charset.forName("UTF-8"));

FileWriter:向文本文件写入内容,支持覆盖和追加模式。构造方法:

java 复制代码
// 覆盖模式
FileWriter fw = new FileWriter("test.txt");
// 追加模式
FileWriter fw = new FileWriter("test.txt", true);
// 指定编码(JDK 11+)
FileWriter fw = new FileWriter("test.txt", Charset.forName("UTF-8"), true);

使用示例:文本文件读写

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

public class FileReaderWriterDemo {
    public static void main(String[] args) {
        FileReader fr = null;
        FileWriter fw = null;
        try {
            // 1. 创建输入流(读取文件)和输出流(写入文件,追加模式)
            fr = new FileReader("source.txt");
            fw = new FileWriter("target.txt", true);
            
            // 2. 定义字符缓冲区
            char[] cbuf = new char[1024];
            int len;
            // 3. 循环读取并写入
            while ((len = fr.read(cbuf)) != -1) {
                fw.write(cbuf, 0, len);
            }
            System.out.println("文本写入成功");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4. 关闭流
            try {
                if (fw != null) fw.close();
                if (fr != null) fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
(2)缓冲字符流:BufferedReader & BufferedWriter

缓冲字符流是对普通字符流的包装,它在内存中开辟了一个缓冲区,读写数据时先操作缓冲区,减少了与磁盘的交互次数,从而大幅提高读写效率 。同时,BufferedReader 提供了 readLine() 方法,可以一次性读取一行文本,非常适合处理按行存储的文本文件。

  • BufferedReader :包装字符输入流,提供缓冲功能和按行读取方法。构造方法:

    java

    运行

    复制代码
    // 包装 FileReader
    BufferedReader br = new BufferedReader(new FileReader("test.txt"));

    特有方法:String readLine():读取一行文本,返回包含该行内容的字符串;如果到达流的末尾,返回 null

  • BufferedWriter :包装字符输出流,提供缓冲功能和换行方法。构造方法:

    java

    运行

    复制代码
    BufferedWriter bw = new BufferedWriter(new FileWriter("test.txt"));

    特有方法:void newLine():写入一个换行符,适配不同操作系统的换行规则(Windows 是 \r\n,Linux 是 \n)。

使用示例:按行读取文本文件

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

public class BufferedReaderDemo {
    public static void main(String[] args) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader("test.txt"));
            String line;
            // 按行读取文本,直到末尾
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (br != null) br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

四、转换流:InputStreamReader & OutputStreamWriter

转换流是字节流和字符流之间的桥梁,它可以将字节流转换为字符流,并在转换过程中指定字符编码,从而解决字符流的编码问题。

  • InputStreamReader :将字节输入流(InputStream)转换为字符输入流(Reader)。
  • OutputStreamWriter :将字节输出流(OutputStream)转换为字符输出流(Writer)。

构造方法中需要传入字节流对象和指定的字符编码:

java

运行

复制代码
// 将 FileInputStream 转换为字符输入流,指定 UTF-8 编码
InputStreamReader isr = new InputStreamReader(new FileInputStream("test.txt"), "UTF-8");

// 将 FileOutputStream 转换为字符输出流,指定 GBK 编码
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("test.txt"), "GBK");

使用场景:当需要读取或写入文本文件,且需要明确指定字符编码时,使用转换流可以避免乱码。例如,读取一个 GBK 编码的文本文件:

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

public class InputStreamReaderDemo {
    public static void main(String[] args) {
        InputStreamReader isr = null;
        try {
            // 指定 GBK 编码读取文件
            isr = new InputStreamReader(new FileInputStream("gbk.txt"), "GBK");
            char[] cbuf = new char[1024];
            int len;
            while ((len = isr.read(cbuf)) != -1) {
                System.out.println(new String(cbuf, 0, len));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (isr != null) isr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

五、缓冲流(Buffered Stream)

缓冲流是对字节流和字符流的增强,它的核心作用是通过缓冲区减少磁盘 IO 次数,提高读写性能。缓冲流分为字节缓冲流字符缓冲流

  • 字节缓冲流BufferedInputStream(包装字节输入流)、BufferedOutputStream(包装字节输出流)。
  • 字符缓冲流BufferedReaderBufferedWriter(前文已介绍)。

缓冲流的使用规则是包装流:创建缓冲流对象时,传入一个普通的字节流或字符流对象,底层的读写操作由缓冲流优化。

字节缓冲流使用示例:

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

public class BufferedStreamDemo {
    public static void main(String[] args) {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            // 包装 FileInputStream 和 FileOutputStream
            bis = new BufferedInputStream(new FileInputStream("source.mp4"));
            bos = new BufferedOutputStream(new FileOutputStream("target.mp4"));
            
            byte[] buffer = new byte[1024];
            int len;
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
            System.out.println("视频文件复制成功");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bos != null) bos.close();
                if (bis != null) bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

六、对象流:ObjectInputStream & ObjectOutputStream

对象流用于实现对象的序列化和反序列化。序列化是指将内存中的 Java 对象转换为字节序列,以便存储到文件或通过网络传输;反序列化则是将字节序列恢复为 Java 对象。

要实现序列化,对象所属的类必须实现 Serializable 接口(这是一个标记接口,没有任何方法),否则会抛出 NotSerializableException 异常。

1. 核心方法

  • ObjectOutputStream (序列化):void writeObject(Object obj):将对象写入输出流。
  • ObjectInputStream (反序列化):Object readObject():从输入流中读取对象,返回值需要强制类型转换。

2. 使用示例

步骤 1:定义可序列化的类

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

// 实现 Serializable 接口
public class Student implements Serializable {
    // 序列化版本号,用于保证序列化和反序列化的类版本一致
    private static final long serialVersionUID = 1L;
    
    private String name;
    private int age;
    
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "Student{name='" + name + "', age=" + age + "}";
    }
}

步骤 2:对象的序列化和反序列化

java 复制代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class ObjectStreamDemo {
    public static void main(String[] args) throws Exception {
        // 1. 序列化:将 Student 对象写入文件
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student.txt"));
        oos.writeObject(new Student("张三", 20));
        oos.close();
        
        // 2. 反序列化:从文件中读取 Student 对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student.txt"));
        Student student = (Student) ois.readObject();
        System.out.println(student); // 输出:Student{name='张三', age=20}
        ois.close();
    }
}

七、Java IO 核心注意事项

八、字节流与字符流的区别

对比维度 字节流 字符流
数据单位 字节(8 位) 字符(16 位)
处理对象 所有文件(文本、图片、视频等) 仅文本文件
编码处理 不处理编码,可能出现乱码 自动处理编码,避免乱码
顶层抽象类 InputStreamOutputStream ReaderWriter
特有方法 readLine()write(String)newLine()
  1. 资源关闭 :流使用完毕后必须调用 close() 方法关闭,否则会造成系统资源泄漏。在 JDK 7 及以上,可以使用 try-with-resources 语法,自动关闭实现了 AutoCloseable 接口的资源,简化代码:

    java 复制代码
    try (FileInputStream fis = new FileInputStream("test.txt");
         FileOutputStream fos = new FileOutputStream("test_copy.txt")) {
        byte[] buffer = new byte[1024];
        int len;
        while ((len = fis.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
  2. 编码问题:处理文本文件时,优先使用字符流或转换流,并明确指定字符编码(如 UTF-8),避免因系统默认编码不同导致乱码。

  3. 缓冲区刷新 :带缓冲区的输出流(如 BufferedOutputStreamBufferedWriter),在数据写入后需要调用 flush() 方法,确保缓冲区中的数据全部写入目标设备。

  4. 序列化注意事项

    • 类的 serialVersionUID 建议显式声明,避免类结构变化后反序列化失败。
    • transient 修饰的成员变量不会被序列化。
相关推荐
MyBFuture1 小时前
C#接口与抽象类:关键区别详解
开发语言·c#·visual studio
晨晖22 小时前
简单排序c语言版
c语言·开发语言
小萌新上大分2 小时前
java线程通信 生产者消费者,synchronized,,ReentrantLock,Condition(笔记备份)
java·多线程·lock·java线程间通信的方式·reentrantlock使用·生产者消费者问题java·java多线程与高并发
それども2 小时前
Spring Bean 的name可以相同吗
java·后端·spring
MediaTea2 小时前
大学 Python 编程基础(合集)
开发语言·python
墨雪不会编程2 小时前
C++ string 详解:STL 字符串容器的使用技巧
java·开发语言·c++
Lucky GGBond2 小时前
实践开发:老系统新增字段我是如何用枚举优雅兼容历史数据的
java
悲喜自渡7212 小时前
Python 编程(gem5 )
java·linux·开发语言
xing-xing3 小时前
JVM 内存、直接内存、系统内存、本地内存、物理内存总结
java·jvm