Java IO流核心知识点与实战总结(学习笔记)
在Java编程中,IO(Input/Output)流是实现程序与外部设备(文件、网络、控制台等)之间数据传输的核心机制。无论是读取配置文件、写入日志,还是网络通信,都离不开IO流的支持。本文结合课堂笔记和实战代码,系统梳理IO流的分类、核心类、常用方法及异常处理,补充关键细节和最佳实践,帮助你快速掌握Java IO编程。
一、IO流的基本概念与分类
1. 什么是流
流(Stream)是一组有序的数据序列,它将数据从一个地方传输到另一个地方。我们可以把流想象成"水管",数据就是水管里的水,输入流是从外部流向程序,输出流是从程序流向外部。
2. 流的核心分类
Java IO流主要按照数据类型 和流向两个维度进行分类:
| 分类维度 | 类型 | 说明 | 适用场景 |
|---|---|---|---|
| 数据类型 | 字节流 | 以8位字节为单位处理数据,处理所有类型的文件 | 二进制文件(图片、音频、视频、exe等) |
| 字符流 | 以16位字符为单位处理数据,基于Unicode编码 | 文本文件(txt、java、html等) | |
| 流向 | 输入流 | 从外部数据源读取数据到程序 | 读取文件、接收网络数据 |
| 输出流 | 将程序中的数据写入到外部目标 | 写入文件、发送网络数据 | |
| 功能 | 节点流 | 直接操作数据源的流(如FileInputStream) | 基础数据读写 |
| 处理流 | 包装节点流,提供额外功能(如BufferedReader) | 提高效率、增强功能 |
3. IO流核心类层次结构
Java IO流的所有类都继承自以下四个抽象基类:
字节流 字符流
InputStream(输入) Reader(输入)
├─ FileInputStream ├─ FileReader
├─ BufferedInputStream ├─ InputStreamReader(转换流)
├─ ZipInputStream ├─ BufferedReader(缓冲流)
└─ ObjectInputStream └─ StringReader
OutputStream(输出) Writer(输出)
├─ FileOutputStream ├─ FileWriter
├─ BufferedOutputStream ├─ OutputStreamWriter(转换流)
├─ ZipOutputStream ├─ BufferedWriter(缓冲流)
├─ ObjectOutputStream ├─ PrintWriter(打印流)
└─ PrintStream
二、字符流:专门处理文本数据
字符流专门用于处理文本文件,它会自动处理字符编码转换,避免乱码问题。所有字符流都继承自Reader(输入)和Writer(输出)抽象基类。
2.1 字符输入流(Reader)
字符输入流用于从文本数据源中读取字符数据,常用实现类有三个:
| 类名 | 作用 | 特点 |
|---|---|---|
FileReader |
从文件中读取字符 | 便捷类,使用系统默认编码 |
InputStreamReader |
字节流转字符流的桥梁 | 可以指定字符编码,解决乱码问题 |
BufferedReader |
带缓冲的字符输入流 | 提供按行读取功能,大幅提高读取效率 |
实战代码:三种字符输入流的使用
java
import java.io.*;
public class ReaderExample {
public static void main(String[] args) {
// 1. FileReader:逐个字符读取(简单但效率低)
try (FileReader reader = new FileReader("D:\\test\\a.txt")) {
int data;
// read()返回-1表示到达流末尾
while ((data = reader.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
}
// 2. InputStreamReader:指定编码读取(解决乱码)
try (InputStreamReader reader = new InputStreamReader(
new FileInputStream("D:\\test\\a.txt"), "UTF-8")) {
int data;
while ((data = reader.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
}
// 3. BufferedReader:按行读取(推荐,效率最高)
try (BufferedReader br = new BufferedReader(
new FileReader("D:\\test\\a.txt"))) {
String line;
// readLine()返回null表示到达流末尾
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.2 字符输出流(Writer)
字符输出流用于将字符数据写入文本目标,常用实现类与输入流对应:
| 类名 | 作用 | 特点 |
|---|---|---|
FileWriter |
向文件中写入字符 | 便捷类,使用系统默认编码 |
OutputStreamWriter |
字符流转字节流的桥梁 | 可以指定字符编码 |
BufferedWriter |
带缓冲的字符输出流 | 提供换行方法,提高写入效率 |
Writer的五种写入方法
Writer类提供了五种重载的write方法,满足不同的写入需求:
| 方法签名 | 功能说明 |
|---|---|
write(int c) |
写入单个字符(低16位有效) |
write(char[] cbuf) |
写入整个字符数组 |
write(char[] cbuf, int off, int len) |
写入字符数组的一部分(从off开始,共len个字符) |
write(String str) |
写入整个字符串 |
write(String str, int off, int len) |
写入字符串的一部分(从off开始,共len个字符) |
实战代码:字符输出流的使用
java
import java.io.*;
public class WriterExample {
public static void main(String[] args) {
// 1. FileWriter:基础写入
try (FileWriter writer = new FileWriter("output.txt")) {
writer.write("Hello, World!");
writer.write("\n这是新的一行"); // \n是换行符
} catch (IOException e) {
e.printStackTrace();
}
// 2. BufferedWriter:带缓冲写入(推荐)
try (BufferedWriter bw = new BufferedWriter(
new FileWriter("output.txt", true))) { // true表示追加写入
bw.write("Hello, Java IO!");
bw.newLine(); // 跨平台换行符(推荐使用)
bw.write("这是追加的内容");
} catch (IOException e) {
e.printStackTrace();
}
// 3. 五种写入方法演示
try (Writer writer = new FileWriter("writer_demo.txt")) {
writer.write('H'); // 单个字符
char[] chars = {'e', 'l', 'l', 'o'};
writer.write(chars); // 整个字符数组
writer.write(chars, 1, 2); // 字符数组的一部分(ll)
writer.write(", World!"); // 整个字符串
writer.write("\nThis is Java IO", 0, 10); // 字符串的一部分(This is Ja)
} catch (IOException e) {
e.printStackTrace();
}
}
}
重要提示:
FileWriter的构造方法第二个参数append设为true时,会在文件末尾追加内容,否则会覆盖原有内容。- 使用
newLine()方法代替\n,可以实现跨平台的换行(Windows是\r\n,Linux是\n)。
三、字节流:处理所有类型的数据
字节流是最基础的流,它以字节为单位处理数据,可以处理任何类型的文件(文本和二进制)。所有字节流都继承自InputStream(输入)和OutputStream(输出)抽象基类。
3.1 字节输入流(InputStream)
InputStream定义了字节输入流的基本方法:
int read():读取一个字节,返回0-255的整数,到达末尾返回-1int read(byte[] b):读取最多b.length个字节到数组,返回实际读取的字节数int read(byte[] b, int off, int len):读取最多len个字节到数组,从off位置开始存储void close():关闭流,释放资源
最常用的实现类是FileInputStream,用于从文件中读取字节数据。
实战代码:字节输入流读取文件
java
import java.io.*;
public class FileInputStreamExample {
public static void main(String[] args) {
// 推荐使用try-with-resources自动关闭流
try (FileInputStream fis = new FileInputStream("D:\\test\\test.jpg")) {
byte[] buffer = new byte[1024]; // 1KB缓冲区
int len;
while ((len = fis.read(buffer)) != -1) {
// 处理读取到的字节数据(这里只是打印长度)
System.out.println("读取了" + len + "个字节");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
最佳实践:使用字节数组作为缓冲区(通常大小为1024或其倍数),可以大幅提高读取效率,避免频繁的磁盘IO操作。
3.2 字节输出流(OutputStream)
OutputStream定义了字节输出流的基本方法:
void write(int b):写入一个字节void write(byte[] b):写入整个字节数组void write(byte[] b, int off, int len):写入字节数组的一部分void close():关闭流,释放资源void flush():刷新缓冲区,将数据立即写入目标
最常用的实现类是FileOutputStream,用于向文件中写入字节数据。
实战代码:字节输出流写入文件
java
import java.io.*;
public class FileOutputStreamExample {
public static void main(String[] args) {
try (FileOutputStream fos = new FileOutputStream("byte_output.txt")) {
fos.write(97); // 写入字节'a'
fos.write("Hello, 字节流!".getBytes()); // 写入字符串的字节数组
fos.write("ABCDEFG".getBytes(), 2, 3); // 写入"CDE"
} catch (IOException e) {
e.printStackTrace();
}
}
}
四、IO流中的异常处理
IO操作几乎都会抛出受检异常(IOException及其子类),必须显式处理。Java提供了两种异常处理方式:
4.1 传统方式:try-catch-finally
在finally块中关闭流,确保无论是否发生异常,资源都会被释放。
java
import java.io.*;
public class TraditionalExceptionHandling {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("test.txt");
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
System.out.println(new String(buffer, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流前必须判断是否为null
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
4.2 推荐方式:try-with-resources(Java 7+)
try-with-resources语法会自动关闭实现了AutoCloseable接口的资源(所有IO流都实现了该接口),代码更简洁、更安全。
java
import java.io.*;
public class TryWithResourcesExample {
public static void main(String[] args) {
// 资源在try括号中声明,会自动关闭
try (FileInputStream fis = new FileInputStream("test.txt");
FileOutputStream fos = new FileOutputStream("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();
}
}
}
重要提示:
- 多个资源之间用分号分隔,会按照声明的逆序自动关闭。
- 这是Java IO编程的最佳实践,强烈推荐使用。
五、关键补充知识点
5.1 字符编码与乱码问题
乱码的根本原因是编码和解码使用的字符集不一致。解决方法:
- 使用
InputStreamReader和OutputStreamWriter显式指定编码(如UTF-8)。 - 避免使用
FileReader和FileWriter处理跨平台的文本文件,因为它们使用系统默认编码。
5.2 缓冲流的工作原理
缓冲流内部维护了一个缓冲区(默认大小8KB),读写数据时先操作缓冲区,当缓冲区满了或流关闭时,才会进行实际的磁盘IO操作。这大大减少了IO次数,提高了性能。
性能对比:使用缓冲流读写文件的速度通常是普通流的10倍以上。
5.3 字节流与字符流的选择原则
- 处理文本文件 :优先使用字符流(
Reader/Writer),自动处理编码。 - 处理二进制文件 (图片、音频、视频等):必须使用字节流(
InputStream/OutputStream),否则会损坏文件。 - 不确定文件类型:使用字节流,它可以处理所有类型的数据。
5.4 其他常用流简介
- 打印流 :
PrintStream和PrintWriter,提供了丰富的打印方法(print()、println()),System.out就是PrintStream的实例。 - 对象流 :
ObjectInputStream和ObjectOutputStream,用于实现对象的序列化和反序列化。 - 压缩流 :
ZipInputStream和ZipOutputStream,用于处理ZIP压缩文件。
六、总结
Java IO流是编程中不可或缺的部分,掌握它的核心要点:
- 分类清晰:按数据类型分为字节流和字符流,按流向分为输入流和输出流。
- 抽象基类 :所有流都继承自
InputStream、OutputStream、Reader、Writer。 - 缓冲优先 :使用缓冲流(
BufferedXxx)可以大幅提高IO性能。 - 异常处理 :优先使用
try-with-resources自动关闭资源,避免资源泄漏。 - 编码问题:处理文本文件时显式指定编码,避免乱码。