java的IO流

java的IO流

  • java的IO流是什么
  • [IO 流的核心分类](#IO 流的核心分类)
    • [字节流 Byte Stream](#字节流 Byte Stream)
      • [字节输入流 InputStream](#字节输入流 InputStream)
      • [字节输出流 OutputStream](#字节输出流 OutputStream)
    • [字符流 Character Stream](#字符流 Character Stream)
      • [字符输入流 Reader](#字符输入流 Reader)
      • [字符输出流 Writer](#字符输出流 Writer)

java的IO流是什么

Java IO(Input/Output,输入 / 输出)流是 JDK java.io 包中用于处理不同数据载体间数据传输的核心抽象机制,实现程序运行时内存与各类外部数据载体之间的标准化数据传输,其适用的交互载体涵盖文件系统、网络套接字、内存缓冲区、控制台等。

从数据交互逻辑来看,程序运行时的核心数据处理均在内存中完成,而实际业务场景中程序需与外部数据载体进行数据交换,IO 流正是连接 程序运行时内存与外部数据载体的标准化桥梁,其核心职责是保障数据在二者间的可靠且高效地读取与写入。

流的本质:以字节(Byte) 或字符(Char) 为基本传输单位的有序数据序列,数据在流中呈现单向流动特性 ------ 输入流(InputStream/Reader)实现 "外部数据载体→程序运行时内存" 的单向数据读取,输出流(OutputStream/Writer)实现 "程序运行时内存→外部数据载体" 的单向数据写入。

IO 流的核心目标:通过抽象层屏蔽不同数据载体底层读写逻辑的实现差异,为程序提供统一、标准化的 IO 操作 API,降低程序与外部数据载体交互的耦合度。

IO 流的核心分类

Java 的 IO 流体系按处理数据的基本单位可划分为字节流(Byte Stream)和字符流(Character Stream)两大核心体系,二者均遵循按流向划分输入与输出流的设计逻辑。

字节流 Byte Stream

字节流是以字节(Byte,8 位二进制数) 为基本单位处理数据的流,其核心特征是直接操作原始二进制数据,不涉及字符编码转换逻辑,仅负责二进制数据的传输。由于计算机中所有数据(文件、图片、视频、文本等)最终均以字节形式存储,因此字节流适用于处理所有类型的二进制数据。

字节流按照数据流向可分为字节输入流和字节输出流:

  • 字节输入流核心抽象类是 InputStream,定义了从外部数据载体读取字节数据的统一方法规范,所有字节输入流实现类均直接或间接继承 InputStream;
  • 字节输出流的核心抽象类是 OutputStream,定义了向外部数据载体写入字节数据的统一方法规范,所有字节输出流实现类均直接或间接继承 OutputStream。

字节输入流 InputStream

InputStream 是 Java IO 体系中所有字节输入流的抽象基类,该类无法直接实例化,仅定义了字节维度读取数据的统一方法规范。

FileInputStream 作为 InputStream 的典型实现类,专门用于读取本地文件系统中的文件数据,可将文件中的二进制数据读取至程序运行时内存中。

FileInputStream 核心构造方法

  • FileInputStream(File file):通过打开与文件系统中 File 对象 file 所表示的实际文件的连接,创建 FileInputStream 实例;若指定文件不存在、是目录而非文件或无访问权限导致无法打开,会抛出 FileNotFoundException。
  • FileInputStream(String name):通过打开与文件系统中路径名 name 所指定的实际文件的连接,创建 FileInputStream 实例;异常规则同上述构造方法。

InputStream 类核心常用方法

  • int read():从输入流中读取单个字节的二进制数据,返回值为该字节对应的无符号整数值(范围 0~255);若已到达流的末尾、无数据可读时返回 -1。
  • int read(byte[] b):从输入流中批量读取字节数据,并将其存储至缓冲区数组 b 中;返回值为实际读取的字节数,若到达流的末尾则返回 -1;若数组 b 为 null,会抛出 NullPointerException。
  • int read(byte[] b, int off, int len):从输入流中读取最多 len 个字节的数据,将其存储至数组 b 中,且从数组的偏移量 off 位置开始写入;返回值为实际读取的字节数,若到达流的末尾则返回 -1;若 off/len 非法(如偏移量超出数组长度、长度为负数),会抛出 IndexOutOfBoundsException。
  • void close():关闭此输入流并释放与之关联的所有系统资源,流关闭后再次调用读写方法会抛出 IOException。

下面我们来代码进行实现

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

public class Test {
    public static void main(String[] args)  {
        FileInputStream fis = null;
        try {
            File file = new File("D:\\hello.txt");
            fis = new FileInputStream(file);
            byte[] buffer = new byte[5];
            int len;
            while ((len=fis.read(buffer))!=-1){
                String str=new String(buffer,0,len);
                System.out.println(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

这段Java代码的功能是读取指定路径 "D:\hello.txt" 的文件内容。代码中通过 File 类定位文件,使用 FileInputStream 创建文件输入流,定义了一个长度为5的字节数组 buffer 作为缓冲区,通过 while 循环每次从文件中读取最多5个字节到缓冲区,中间变量 len 记录读取的有效字节数,new String(buffer, 0, len) 中的 (buffer, 0, len) 是为了精准提取缓冲区中本次实际读取的有效数据,避免混入无效的旧数据,再将读取到的有效字节转换为字符串打印,直到读取到文件末尾(返回-1)。同时使用 try-catch 处理可能出现的IO异常,并用 finally 块确保文件输入流最终被关闭,以释放系统资源。

字节输出流 OutputStream

OutputStream 是 Java IO 体系中所有字节输出流的抽象基类,无法直接实例化,定义了字节维度写入数据的统一方法规范,所有字节输出流实现类均需遵循该规范实现具体的写入逻辑。

FileOutputStream 作为 OutputStream 的典型实现类,专门用于将程序运行时内存中的二进制数据写入本地文件系统的文件,可实现数据从程序到文件的输出。

FileOutputStream 核心构造方法

  • FileOutputStream(File file):创建文件输出流,用于向 File 对象表示的文件写入数据;若文件不存在则尝试创建,若文件存在则默认覆盖原有内容,若文件为目录 / 无写入权限则抛出异常。
  • FileOutputStream(String name):创建文件输出流,用于向路径名 name 指定的文件写入数据;覆盖规则、异常场景同上述构造方法。
  • FileOutputStream(String name, boolean append):创建文件输出流,append 参数控制写入模式 ------true 表示追加写入(数据写入文件末尾,不覆盖原有内容),false 表示覆盖写入;异常场景同前。

OutputStream 类核心常用方法(均可能抛出 IOException)

  • void write(int b):将指定的字节写入输出流(参数为 int 类型,实际仅写入低 8 位的二进制字节数据,高 24 位会被忽略)。
  • void write(byte[] b):将字节数组 b 中的所有字节写入输出流;若数组 b 为 null,会抛出 NullPointerException。
  • void write(byte[] b, int off, int len):将字节数组 b 中从偏移量 off 开始的 len 个字节写入输出流;若 off/len 非法(如偏移量超出数组长度、长度为负数),会抛出 IndexOutOfBoundsException。
  • void flush():刷新此输出流,强制将缓冲区中未写入的字节立即写入目标载体(FileOutputStream 无缓冲,此方法仅为兼容父类规范)。
  • void close():关闭此输出流并释放与之关联的所有系统资源(如文件句柄);关闭后调用写入方法会抛出 IOException。

下面我们用代码进行实现

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

public class Test {

	public static void main (String[] args)  {
        FileOutputStream fos =null;
        try {
            File file = new File("D:\\hello.txt");
            fos = new FileOutputStream(file);
            fos.write(97);
            fos.write("中国人!\r\n".getBytes());
            fos.write("ABCDEFGH".getBytes(),2,4);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //释放资源
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

这段Java代码的功能是在指定路径文件中写下内容。代码中首先定义了文件输出流 FileOutputStream 对象,在 try 块中创建指向 "D:\hello.txt" 的文件对象并初始化输出流,随后通过三次 write 方法写入数据,第一次写入ASCII码为97的字符'a',第二次写入字符串"中国人!\r\n"(其中\r\n表示回车换行),第三次从字符串"ABCDEFGH"的字节数组中,从索引2开始取4个字节(即"CDEF")写入;catch 块用于捕获可能出现的IO异常并打印异常信息,finally 块则确保无论是否发生异常,都关闭输出流以释放资源。

字节输入流和字节输出流的组合实践:拷贝功能的实现

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

public class Test {
	public static void main(String[] args) throws IOException{
	FileInputStream fis = new FileInputStream("D:\\movie.mp4");
	FileOutputStream fos = new FileOutputStream("D:\\movie1.mp4");
	int len;
	byte[] bytes = new byte[1024 * 1024 * 5];
	while((len = fis.read(bytes)) != -1){
		fos.write(bytes,0,len);
	}
	fos.close();
	fis.close();
	}
}

这段 Java 代码的主要功能是复制文件,将D:\movie.mp4这个视频文件复制一份,保存为D:\movie1.mp4。创建一个文件输入流,用于读取源文件D:\movie.mp4,再创建一个文件输出流,用于向目标文件写入数据,如果目标文件不存在,会自动创建,如果已存在,会覆盖原有内容。byte[] bytes 定义一个字节数组作为缓冲区,大小为5MB,使用缓冲区一次读取写入大量数据,比逐个字节操作更快,减少磁盘 IO 次数,提高复制效率。将缓冲区中从0索引开始、长度为len的字节写入输出流,只写实际读取的字节,避免最后一次读取时缓冲区未满导致的冗余数据。

字符流 Character Stream

字符流是以 16 位 Unicode 字符(char 类型)为基本数据处理单位完成读写操作,适用于所有文本格式文件的处理。

在 Java IO 体系中,字符流是专门面向文本数据处理的流类型,本质上是在字节流的基础上增加了字符编码转换机制:底层仍依托字节流完成数据的底层传输,同时增加了基于指定字符编码(如 UTF-8、GBK)的转换逻辑 ------ 读取时将外部字节序列按指定编码解码转换为 16 位 Unicode 字符序列(Java char 类型默认采用 UTF-16 编码),写入时将程序中的 Unicode 字符序列转换为对应编码的字节序列,从根本上避免文本处理中因编码不统一导致的乱码问题。

按数据流向可将字符流划分为字符输入流和字符输出流两类:

  • 字符输入流:核心抽象基类为 Reader,定义了从外部数据载体读取字符数据的统一方法规范,所有字符输入流实现类均直接或间接继承 Reader;
  • 字符输出流:核心抽象基类为 Writer,定义了向外部数据载体写入字符数据的统一方法规范,所有字符输出流实现类均直接或间接继承 Writer。

字符输入流 Reader

Reader 是所有字符输入流的抽象基类,无法直接实例化,定义了字符维度读取数据的统一方法规范,以下是其典型实现类:

  • InputStreamReader:是字节输入流到字符输入流的桥梁(核心转换类),接收字节输入流,并将字节序列按指定字符编码(默认系统编码)解码为字符序列;可通过构造方法显式指定编码,解决文本读取乱码问题。
  • FileReader:是 InputStreamReader 的子类,默认使用系统平台编码读取文件中的字符数据;因编码不可自定义,跨平台场景易出现乱码,推荐优先使用 InputStreamReader + FileInputStream 组合。
  • BufferedReader:带缓冲区的字符输入流(处理流),通常包装 InputStreamReader/FileReader 等基础字符流;通过缓冲区批量读取字符数据大幅提升效率,新增 readLine() 方法支持按行读取文本(返回 null 表示读取到流末尾)。

我们来用代码实现

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

public class Test {
    public static void main(String[] args) {
        // 使用 FileReader 读取文件
        try (FileReader reader = new FileReader("D:\\a.txt")) {
            int data;
            while ((data = reader.read()) != -1) {
                System.out.print((char) data); // 逐个字符读取
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 使用 InputStreamReader 读取文件 
        try (InputStreamReader reader = new InputStreamReader(new FileInputStream("D:\\a.txt"), "UTF-8")) {
            int data;
            while ((data = reader.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 使用 BufferedReader 按行读取文件
        try (BufferedReader br = new BufferedReader(new FileReader("D:\\a.txt"))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line); // 逐行读取
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这段Java代码的主要功能是演示三种不同的文件读取方式并打印文件内容。代码中,首先使用 FileReader 以默认编码逐个字符读取指定路径的文件内容并输出;接着使用 InputStreamReader 结合 FileInputStream,先通过 FileInputStream 字节流读取文件的字节数据,无论文件是什么类型,都以字节形式读取,再通过 InputStreamReader 将字节流转换为字符流,这里显式指定编码为"UTF-8",确保按 UTF-8 编码解析字节为字符并输出该文件;通过 new BufferedReader 创建缓冲流,这里包装了FileReader,缓冲区会一次性读取多个字符到内存,减少 IO 操作次数,提供 readLine() 方法,以按行读取的方式获取文件内容并打印;三种方式均采用 try-with-resources 语法自动管理资源,确保文件流在操作完成后正确关闭,同时通过catch块捕获并打印可能出现的 IOException 异常。

字符输出流 Writer

Writer 是所有字符输出流的抽象基类,无法直接实例化,定义了字符维度写入数据的统一方法规范,以下是其典型实现类:

  • OutputStreamWriter:是字符输出流到字节输出流的桥梁(核心转换类),接收程序中的字符序列,按指定字符编码(默认系统编码)编码为字节序列后写入字节输出流;可显式指定编码,避免文本写入乱码。
  • FileWriter:是 OutputStreamWriter 的子类,默认使用系统平台编码向文件写入字符数据;编码不可自定义,跨平台场景易乱码,推荐优先使用 OutputStreamWriter + FileOutputStream 组合。
  • BufferedWriter:带缓冲区的字符输出流(处理流),通常包装 OutputStreamWriter/FileWriter 等基础字符流;通过缓冲区批量写入字符数据提升效率,新增 newLine() 方法支持写入跨平台换行符(适配 Windows/Linux 换行规则)。
java 复制代码
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;

public class Test {

    public static void main(String[] args) {
    
    	// 使用 FileWriter 写入文件
        try{
            FileWriter writer = new FileWriter("output.txt");
            writer.write("Hello, World!"); 
            writer.write("\nThis is a new line.");
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        
		// 使用 OutputStreamWriter 写入文件
        try {
            OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream("output.txt"), StandardCharsets.UTF_8);
            writer.write("Hello, World!");
            writer.write("\nThis is a new line.");
            writer.close();
        }catch (IOException e){
            e.printStackTrace();
        }

        // 使用 BufferedWriter 按行写入文件
        try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
            bw.write("Hello, World!");
            bw.newLine();
            bw.write("This is a new line.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这段Java代码主要演示了三种不同的文件写入方式,均向"output.txt"文件写入相同的内容。第一种使用 FileWriter 直接进行字符写入,需手动调用 close() 方法确保数据写入文件,这是关键步骤,FileWriter 内部有缓冲区,写入的数据会先存到缓冲区,只有调用 close() 或 flush() 时,缓冲区的数据才会真正写入文件,若忘记关闭,可能导致数据丢失;第二种通过 OutputStreamWriter 包装 FileOutputStream 实现写入,并指定使用UTF_8的形式编码,同样需要手动关闭流;第三种使用 BufferedWriter 本身也需要手动关闭流,但是结合 try-with-resources 语法提供了更高效的缓冲写入还能自动关闭资源。前面两种形式只能手动写入"\n"换行符,而 BufferedWriter 通过 newLine() 方法能够更规范地处理换行。需要注意的是,这三种方式对同一文件的写入是覆盖式的,最终文件内容由最后一次写入操作决定。

字符输出流调用 close() 的核心目的是 确保缓冲区数据完整写入和释放系统资源

字符输出流内部都维护了大小为8192 个字符的默认缓冲区,当通过 write() 方法写入数据时,数据会先暂存在缓冲区中,而非立即写入文件,只有当缓冲区填满、手动调用 flush() 或调用 close() 时,缓冲区中的数据才会按照编码被转换为字节并写入到底层文件,close() 方法在关闭流之前会自动触发 flush(),确保缓冲区中剩余数据不会丢失。close() 方法的另一个核心作用就是释放这些底层资源。

基础字符输出流存在缓冲区,为什么还设计了 BufferedWriter ?

基础字符输出流的缓冲区是为了编码转换临时服务的,而 BufferedWriter 的缓冲区是为了高效批量写入专门设计的,缓冲区容量更大,前者解决字符→字节的转换问题,后者解决频繁 IO 导致的性能问题,两者定位互补。对于大文件写入或频繁写入小数据的场景,BufferedWriter 的效率优势会被放大。

相关推荐
w-w0w-w21 小时前
C++泛型编程
开发语言·c++·算法
什么都不会的Tristan21 小时前
Feed流(关注推送)
java·前端·数据库
YJlio21 小时前
PsPing 学习笔记(14.8):常见错误与排障实战(超时、拒绝连接、权限问题)
开发语言·笔记·python·学习·django·pdf·pygame
网小鱼的学习笔记21 小时前
面试题1:==和equals的比较
java·jvm·面试
LegendNoTitle21 小时前
Windows和Linux下Rust-init、Cargo下载慢的解决
开发语言·windows·rust
亓才孓21 小时前
深浅拷贝--Java
java·开发语言·windows
潲爺21 小时前
Java笔记总结
java·开发语言·笔记·学习
菜的不敢吱声21 小时前
swift学习第一天
开发语言·学习·swift
培林将军21 小时前
C语言指针
c语言·开发语言·算法