在Java中,IO流(Input/Output Stream) 是进行数据输入与输出处理的基础机制,广泛应用于文件读写、网络通信、序列化等场景。IO流体系庞大,但本质可分为两大类:字节流和字符流。本文将围绕这两个核心概念,系统讲解其原理、使用方法和适用场景,帮助读者深入理解Java的IO流机制。
一、IO流的总体结构
Java的IO流体系以数据处理单位进行分类:
分类 | 处理单位 | 抽象基类 |
---|---|---|
字节流 | 8位(1字节) | InputStream / OutputStream |
字符流 | 16位(2字节) | Reader / Writer |
-
字节流适合处理原始的二进制数据,不涉及字符编码。
-
字符流适合处理字符数据,支持编码转换(如 UTF-8、GBK)。
从数据方向上又可以划分为:
-
输入流(Input):从外部读取数据到程序中;
-
输出流(Output):将数据从程序写出到外部设备。
因此,Java中有四个基本抽象类:
-
InputStream
:字节输入流的基类 -
OutputStream
:字节输出流的基类 -
Reader
:字符输入流的基类 -
Writer
:字符输出流的基类
二、字节流:以字节为单位的数据传输
字节流是 Java IO 流体系中最基本的形式之一,以8位字节(byte)为单位进行数据的读写操作。它主要用于处理非文本数据,如图片、音频、视频、压缩文件等二进制数据,也可以处理文本,但不能直接识别字符编码。
2.1 字节流的核心类
Java 中,所有字节流都继承自以下两个抽象基类:
抽象基类 | 用途 |
---|---|
InputStream | 字节输入流,读取数据 |
OutputStream | 字节输出流,写入数据 |
1. 常用输入流(继承自 InputStream)
类名 | 功能描述 |
---|---|
FileInputStream | 从文件中读取字节数据 |
BufferedInputStream | 提供缓冲功能,提高读取效率 |
DataInputStream | 读取 Java 基本数据类型(int、float 等) |
ObjectInputStream | 读取对象,实现反序列化 |
ByteArrayInputStream | 从内存中的字节数组读取数据 |
2. 常用输出流(继承自 OutputStream)
类名 | 功能描述 |
---|---|
FileOutputStream | 将字节数据写入文件 |
BufferedOutputStream | 提供缓冲功能,提高写入效率 |
DataOutputStream | 写入 Java 基本数据类型 |
ObjectOutputStream | 写入对象,实现序列化 |
ByteArrayOutputStream | 将数据写入内存中的字节数组 |
2.2 字节流的基本方法
所有字节流类通常都包含以下常用方法:
输入流(InputStream)
int read() // 读取一个字节(0~255),返回 -1 表示读取结束
int read(byte[] b) // 读取多个字节填充到数组中
int read(byte[] b, int off, int len) // 从偏移位置读取指定长度
void close() // 关闭流
输出流(OutputStream)
void write(int b) // 写入一个字节
void write(byte[] b) // 写入字节数组
void write(byte[] b, int off, int len) // 写入部分字节数组
void flush() // 刷新缓冲区
void close() // 关闭流
2.3 典型使用案例
示例 1:复制一个文件(使用字节流)
import java.io.*;
public class FileCopyDemo {
public static void main(String[] args) {
try (
FileInputStream fis = new FileInputStream("source.jpg");
FileOutputStream fos = new FileOutputStream("copy.jpg");
) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
System.out.println("文件复制成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
示例 2:读取文件中的前几个字节
FileInputStream fis = new FileInputStream("example.bin");
int byte1 = fis.read();
int byte2 = fis.read();
System.out.println("前两个字节:" + byte1 + ", " + byte2);
fis.close();
2.4 注意事项和使用场景
注意事项:
-
字符编码问题:
字节流不会自动处理字符编码,直接读取文本时可能出现乱码。处理文本推荐使用字符流或InputStreamReader
进行编码转换。 -
资源关闭:
使用流时建议采用 try-with-resources 自动关闭,避免资源泄露。 -
缓冲提高性能:
对于大量数据读写,建议使用BufferedInputStream
和BufferedOutputStream
包装原始流。 -
效率优化:
使用合理大小的缓冲数组(如 1024 或 8192 字节)可以显著提升性能。
字节流适用于所有数据类型,尤其在以下场景中广泛使用:
-
读取/写入二进制文件(如
.jpg
,.mp3
,.zip
等) -
网络传输文件(如 socket 编程中传输文件)
-
序列化与反序列化(将对象转换为字节或从字节恢复为对象)
-
内存流操作(字节数组作为输入输出源)
Java 字节流以 byte 为单位,适合处理任意类型的数据,特别是二进制文件。它是 IO 流体系中最通用的底层流,构成了处理流的基础。通过配合处理流,字节流可以满足从简单读写文件到复杂对象序列化等多种需求。
三、字符流:以字符为单位的数据处理
字符流是专门用于处理文本数据(如 .txt
、.java
、.xml
等)的 I/O 流。它以 字符(char)为单位读取或写入数据,能够自动处理字符编码转换,适合操作 纯文本内容。
Java 中字符流的本质是对字节流的封装,通过字符编码(如 UTF-8、GBK)将字节转换为字符或反之。
3.1 字符流的核心类
Java 中的字符流体系是基于两个抽象基类构建的:
抽象基类 | 功能 |
---|---|
Reader | 字符输入流,读取字符 |
Writer | 字符输出流,写入字符 |
1. 常用输入类(继承自 Reader)
类名 | 功能描述 |
---|---|
FileReader | 从文件中读取字符(默认编码) |
BufferedReader | 带缓冲功能,支持按行读取 |
InputStreamReader | 将字节流转换为字符流,可指定编码 |
CharArrayReader | 从字符数组读取字符 |
StringReader | 从字符串读取字符 |
2. 常用输出类(继承自 Writer)
类名 | 功能描述 |
---|---|
FileWriter | 向文件中写入字符(默认编码) |
BufferedWriter | 提供缓冲功能,支持按行写入 |
OutputStreamWriter | 将字符流转换为字节流,可指定编码 |
CharArrayWriter | 写入字符到字符数组 |
StringWriter | 写入字符到字符串 |
3.2 字符流常用方法
Reader
类常用方法(字符输入流):
int read() // 读取一个字符,返回其 Unicode 编码,读完返回 -1
int read(char[] cbuf) // 读取多个字符到字符数组
int read(char[] cbuf, int offset, int length) // 从指定位置读入指定长度
void close() // 关闭流
Writer
类常用方法(字符输出流):
void write(int c) // 写入单个字符
void write(char[] cbuf) // 写入字符数组
void write(String str) // 写入字符串
void flush() // 刷新流
void close() // 关闭流
3.3 典型用例
示例 1:读取文本文件并逐行输出
import java.io.*;
public class ReadTextFile {
public static void main(String[] args) {
try (
BufferedReader reader = new BufferedReader(new FileReader("example.txt"));
) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
示例 2:写入字符串到文件
import java.io.*;
public class WriteTextFile {
public static void main(String[] args) {
try (
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"));
) {
writer.write("你好,字符流!");
writer.newLine(); // 写入换行符
writer.write("适合处理纯文本数据。");
} catch (IOException e) {
e.printStackTrace();
}
}
}
示例 3:使用指定编码读取文件
InputStreamReader reader = new InputStreamReader(new FileInputStream("utf8.txt"), "UTF-8");
BufferedReader br = new BufferedReader(reader);
String line = br.readLine();
br.close();
3.4 注意事项和使用场景
注意事项
-
字符流只适合处理文本数据,不要用于处理图片、音频、视频等二进制文件,否则数据会损坏。
-
字符编码问题需留意,不同平台或文件可能采用不同编码(如 UTF-8、GBK),使用
InputStreamReader
/OutputStreamWriter
可以指定编码。 -
建议搭配缓冲流使用,如
BufferedReader
和BufferedWriter
,可以大幅提升效率。 -
资源关闭可使用 try-with-resources,简洁且安全,防止资源泄露。
字符流专用于处理文本数据,常见的使用场景包括:
-
读取/写入文本文件(如
.txt
,.csv
,.html
等) -
实现逐行读取、写入
-
读取配置文件、日志文件等字符信息
-
与
InputStreamReader
、OutputStreamWriter
搭配使用以控制字符编码
四、字节流与字符流的区别
4.1处理单位不同
-
字节流(Byte Stream) 以字节(8 bit)为单位处理数据,适用于所有类型文件(如二进制文件、图片、音频等)。 核心抽象类:
InputStream
(输入)和OutputStream
(输出)。 示例实现:FileInputStream
,FileOutputStream
。 -
字符流(Character Stream) 以字符(Char, 16 bit Unicode)为单位处理数据,专门为文本文件设计,自动处理字符编码(如 UTF-8、GBK)。 核心抽象类:
Reader
(输入)和Writer
(输出)。 示例实现:FileReader
,FileWriter
。
4.2底层机制不同
-
字节流直接操作字节 直接读取或写入原始字节,不涉及编码转换。
-
字符流依赖字节流 + 编码表 字符流底层通过字节流读取数据,再根据编码规则将字节转换为字符(如
InputStreamReader
将字节流包装为字符流)。 示例:
// 字节流 + 指定编码转换为字符流 Reader reader = new InputStreamReader(new FileInputStream("file.txt"), "UTF-8");
4.3 使用场景不同
-
字节流
- 处理二进制文件(如图片、视频、压缩包等)。
- 需要直接操作原始字节的场景(如网络传输、加密数据)。
-
字符流
- 处理文本文件(如 .txt、.csv、.json 等),避免手动处理编码问题。
- 需注意:字符流的默认编码可能与系统相关(如 Windows 默认 GBK),建议显式指定编码。
4.4 性能与缓冲不同
-
字节流 默认无缓冲区,需手动包装为
BufferedInputStream
/BufferedOutputStream
提升性能。 -
字符流 多数实现自带缓冲区(如
BufferedReader
/BufferedWriter
),适合逐行读取文本。
示例对比:
// 字节流读取文件(可能需手动处理编码)
try (FileInputStream fis = new FileInputStream("file.txt")) {
byte[] bytes = fis.readAllBytes();
String text = new String(bytes, StandardCharsets.UTF_8); // 需指定编码
}
// 字符流读取文件(自动处理编码)
try (FileReader fr = new FileReader("file.txt", StandardCharsets.UTF_8)) {
BufferedReader br = new BufferedReader(fr);
String line = br.readLine(); // 逐行读取
}
关键总结
特性 | 字节流 | 字符流 |
---|---|---|
处理单位 | 字节(8 bit) | 字符(16 bit Unicode) |
适用文件类型 | 所有类型(尤其是二进制文件) | 文本文件 |
编码处理 | 需手动转换(如 new String(byte[], charset) ) | 自动处理(需指定正确编码) |
核心类 | InputStream , OutputStream | Reader , Writer |
性能优化 | 需手动缓冲(如 BufferedInputStream ) | 自带缓冲(如 BufferedReader ) |
五、流的高级用法:转换流与缓冲流
5.1 什么是转换流
转换流是连接 字节流与字符流之间的桥梁,可以将字节数据“转换”为字符数据,常用于解决字符编码问题。
Java 中提供了两个核心类:
类名 | 说明 |
---|---|
InputStreamReader | 将 字节输入流 → 字符输入流 |
OutputStreamWriter | 将 字符输出流 → 字节输出流 |
⭐ 默认编码是平台编码(如 Windows 是 GBK,Linux 是 UTF-8),可以通过构造方法指定编码。
5.2 构造方法
// 字节流 → 字符流(指定编码)
InputStreamReader isr = new InputStreamReader(new FileInputStream("file.txt"), "UTF-8");// 字符流 → 字节流(指定编码)
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("file.txt"), "UTF-8");
5.3 使用示例:解决中文乱码
import java.io.*;
public class ConvertStreamDemo {
public static void main(String[] args) throws IOException {
// 写入文本,使用 UTF-8 编码
try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream("test.txt"), "UTF-8")) {
writer.write("你好,世界!");
}
// 读取文本,必须使用相同编码
try (InputStreamReader reader = new InputStreamReader(new FileInputStream("test.txt"), "UTF-8")) {
int ch;
while ((ch = reader.read()) != -1) {
System.out.print((char) ch);
}
}
}
}
5.4 使用场景
-
网络传输中读取服务器返回的文本内容(如 HTTP 响应)
-
读取/写入有特定编码的文件(如 UTF-8, GBK)
-
实现跨平台文本数据读写
5.5 什么是缓冲流
缓冲流是对节点流(如 FileInputStream、FileReader)的增强包装类,内部维护了一个缓冲区,避免每次读写都直接操作硬盘,从而提升性能。
字节缓冲流 | 字符缓冲流 |
---|---|
BufferedInputStream | BufferedReader |
BufferedOutputStream | BufferedWriter |
📌 所有缓冲流都通过 包装原始流 来使用,不能单独操作文件。
缓冲流原理
-
内部维护一个默认大小为 8KB 的缓冲区(可手动指定)
-
读取时:先从缓冲区读,缓冲区空了再从文件中读取
-
写入时:先写入缓冲区,缓冲区满了或
flush()
时才写入文件
5.6 使用示例
(1)读取文件的每一行
import java.io.*;
public class BufferedReaderDemo {
public static void main(String[] args) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader("text.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line); // 每次一行
}
}
}
}
(2)写入多行文本
import java.io.*;
public class BufferedWriterDemo {
public static void main(String[] args) throws IOException {
try (BufferedWriter bw = new BufferedWriter(new FileWriter("text.txt"))) {
bw.write("第一行");
bw.newLine(); // 写入换行符
bw.write("第二行");
}
}
}
5.7 总结
特性/流类型 | 转换流 | 缓冲流 |
---|---|---|
功能 | 处理字符编码转换 | 提高读写效率,减少硬盘操作 |
是否包装流 | 是(包装字节流) | 是(包装字节/字符流) |
常用场景 | 网络数据、跨平台文件 | 大量文本数据、高性能文件读写 |
是否支持 readLine | 否(需手动处理) | 是(BufferedReader.readLine() ) |