前言
在Java程序运行过程中,经常需要和外部设备交换数据:读取本地文件内容、向控制台打印信息、网络收发数据、读写内存缓存等。
如果没有统一的数据传输模型,每种设备都要单独编写一套读写逻辑,代码会极度冗余且难以维护。
Java IO(输入输出流)应运而生,它把所有数据传输行为抽象成流,统一一套API完成所有IO操作。接下来本文分为两大块:第一部分讲解流的核心作用,第二部分多角度划分所有IO流,最后附上实操代码示例。
一、IO流的核心作用
1. 统一封装数据传输逻辑
无论是文件、键盘、显示器、网络Socket、内存数组,全部统一抽象为“流”对象。开发者不需要区分底层设备差异,调用read()、write()通用方法即可完成读写,降低学习与开发成本。
2. 实现数据持久化与临时交互
输出流:将程序内存中的数据写出到外部(文件、控制台),实现数据持久保存;
输入流:把外部设备的数据读取到程序内存中,供程序运算、解析使用。
3. 支持分段缓冲读写,提升性能
流底层自带缓冲机制,不会每次读写都直接操作磁盘/网络,批量传输减少IO交互次数,大幅提升文件读写、数据传输效率。
4. 适配多场景数据处理
覆盖日常开发绝大多数场景:
控制台输入输出(Scanner、System.in、System.out)
本地文件读写(文档、图片、视频、配置文件)
网络通信数据传输(Socket流)
内存数组、字符串临时读写
对象序列化与反序列化(对象流)
二、IO流的多种划分方式
2.1 按数据传输方向划分(最基础分类)
核心区分:程序内存 ↔ 外部资源
输入流(读)
数据流向:外部设备 → 程序内存
作用:读取外部数据到程序中
顶层父类:
字节输入流:InputStream
字符输入流:Reader
典型实现:FileInputStream、FileReader、BufferedReader
输出流(写)
数据流向:程序内存 → 外部设备
作用:把程序数据写出保存
顶层父类:
字节输出流:OutputStream
字符输出流:Writer
典型实现:FileOutputStream、FileWriter、PrintWriter
2.2 按数据传输单位划分(字节流 / 字符流)
1. 字节流
传输单位:1字节(8bit),处理所有二进制数据
顶层父类:InputStream / OutputStream
适用场景:图片、视频、音频、压缩包、exe程序等所有非文本文件
特点:不处理编码,直接原始二进制传输,无字符转换
2. 字符流
传输单位:1字符(根据编码1~3字节),专门处理文本
顶层父类:Reader / Writer
适用场景:txt、java、html、配置文件等纯文本
特点:内置编码转换,自动处理中文乱码问题,仅能操作文本
2.3 按功能层级划分:节点流 vs 处理流
1. 节点流(底层流)
直接对接数据源/目标,是真正操作文件、设备的基础流
示例:
文件节点流:FileInputStream、FileReader、FileOutputStream、FileWriter
数组节点流:ByteArrayInputStream、CharArrayReader
2. 处理流(包装流/缓冲流)
不直接对接数据源,包裹节点流,增强原有流功能
常见类型:
缓冲流:BufferedInputStream、BufferedReader,增加缓冲区提升速度
转换流:InputStreamReader、OutputStreamWriter,字节流转字符流,指定编码
打印流:PrintStream、PrintWriter,提供便捷print/println方法
对象流:ObjectInputStream、ObjectOutputStream,实现对象序列化
2.4 特殊分类:标准IO流(控制台流)
Java预设三个全局标准流,无需手动创建:
System.in:标准输入流(键盘输入,字节流)
System.out:标准输出流(控制台打印,打印流)
System.err:标准错误输出流(错误信息打印)
三、各类流适用场景总结
读写图片、视频 → 字节流 FileInputStream / FileOutputStream
读写txt、代码、配置文件 → 字符流 FileReader / FileWriter + 缓冲流
字节流读取文本需指定编码 → 转换流 InputStreamReader
快速打印数据、自动换行 → PrintWriter
读写对象、集合持久化 → Object流
大批量文件读写提速 → Buffered缓冲流
四、实操代码示例
示例1:字节流复制图片(二进制文件)
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 字节流:复制图片/视频等二进制文件
*/
public class ByteStreamCopyDemo {
public static void main(String[] args) {
// 源文件路径、目标文件路径
String srcPath = "D:/test.jpg";
String destPath = "D:/test_copy.jpg";
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(srcPath);
fos = new FileOutputStream(destPath);
// 缓冲数组,一次读写1024字节
byte[] buffer = new byte[1024];
int len;
// 循环读取,len为实际读到的字节数
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
System.out.println("图片复制完成!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流,释放资源
try {
if (fos != null) fos.close();
if (fis != null) fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
示例2:字符缓冲流读写文本文件(处理中文)
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/**
* 字符缓冲流:读写txt文本,支持按行读取
*/
public class CharBufferReadWriteDemo {
public static void main(String[] args) {
String filePath = "D:/demo.txt";
// 写入文本
try (BufferedWriter bw = new BufferedWriter(new FileWriter(filePath))) {
bw.write("Java IO流学习笔记");
bw.newLine(); // 换行
bw.write("字节流处理二进制,字符流处理文本");
bw.flush(); // 刷新缓冲区
} catch (IOException e) {
e.printStackTrace();
}
// 读取文本
try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
String line;
// 逐行读取
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
示例3:转换流(字节流转字符流,指定编码防乱码)
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;
/**
* 转换流:字节流转字符流,手动指定UTF-8编码
*/
public class ConvertStreamDemo {
public static void main(String[] args) {
try (
// 字节节点流
FileInputStream fis = new FileInputStream("D:/utf8.txt");
// 转换流,指定编码
InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
// 包装缓冲流提升效率
BufferedReader br = new BufferedReader(isr)
) {
String content;
while ((content = br.readLine()) != null) {
System.out.println(content);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
示例4:标准流控制台输入输出
import java.util.Scanner;
/**
* 标准IO流:控制台键盘输入、控制台输出
*/
public class StandardIODemo {
public static void main(String[] args) {
// System.in 标准输入流,Scanner封装简化读取
Scanner sc = new Scanner(System.in);
System.out.print("请输入姓名:");
String name = sc.next();
// System.out 标准输出流
System.out.println("你输入的姓名:" + name);
sc.close();
}
}
五、总结
流的核心作用:统一抽象所有IO数据传输,简化不同设备读写操作,兼顾性能与易用性;
三大核心划分维度:流向(输入/输出)、传输单位(字节/字符)、层级(节点/处理);
开发选择流的核心原则:文本优先字符流,二进制文件必须字节流,大批量读写搭配缓冲流提升效率;
编码乱码解决方案:使用转换流手动指定文件编码,避免平台默认编码差异问题。
文末拓展思考
字节流和字符流能否互相转换?转换流的底层原理是什么?
使用缓冲流后为什么必须调用flush()或close()才能保证数据写入?
对象流序列化时transient关键字会产生什么效果?
补充小贴士
JDK7+支持try-with-resources语法,流会自动关闭,省去finally手动释放代码;
所有IO操作都受检IOException,必须捕获或抛出;
字符流仅适用于文本,图片、视频等二进制文件强行用字符流会损坏文件。