1.IO流概述
什么是IO流
- IO流是处理设备上数据的读取和写入的一种方式。数据输入到计算机内存的过程即输入,反之输出到外部存储(比如数据库,文件,远程主机)的过程即输出。
- Java IO流主要包含输入流(InputStream)和输出流(OutputStream),用于对文件或网络等进行数据读写操作。
分类
- 按流向分:
- 输入流:
InputStream
和Reader
- 输出流:
OutputStream
和Writer
- 输入流:
- 按处理单元分:
- 字节流:以字节(8位)为单位进行读写,适合读取二进制文件(如图片、音频、视频等)
InputStream
、OutputStream
- 字符流:以字符(字符(16位 Unicode))为单位进行读写,适合文本文件(如txt文件)
Reader
、Writer
- 字节流:以字节(8位)为单位进行读写,适合读取二进制文件(如图片、音频、视频等)
- 按流向分:
2.Java IO常用类图谱
1.字节流顶层抽象类:
InputStream
OutputStream
1.子类:
子类名 | 用途说明 |
---|---|
FileInputStream |
从文件系统中的某个文件读取字节流 |
ByteArrayInputStream |
从字节数组中读取数据 |
BufferedInputStream |
给其他输入流添加缓冲功能,提高读取效率 |
DataInputStream |
支持读取 Java 基本数据类型(如 int , double 等) |
ObjectInputStream |
支持反序列化对象(需配合 Serializable 接口使用) |
PipedInputStream |
与 PipedOutputStream 配合,实现线程间通信 |
InputStreamReader |
将字节流转换为字符流(配合 Reader 使用) |
GZIPInputStream |
用于解压缩 GZIP 格式的数据 |
InflaterInputStream |
用于解压缩 ZIP 或 Deflate 格式的数据 |
SequenceInputStream |
合并多个输入流,按顺序读取 |
子类名 | 用途说明 |
---|---|
FileOutputStream |
向文件系统中的某个文件写入字节流 |
ByteArrayOutputStream |
向字节数组中写入数据(内存操作) |
BufferedOutputStream |
给其他输出流添加缓冲功能,提高写入效率 |
DataOutputStream |
支持写入 Java 基本数据类型(如 int , double 等) |
ObjectOutputStream |
支持序列化对象(需配合 Serializable 接口使用) |
PipedOutputStream |
与 PipedInputStream 配合,实现线程间通信 |
OutputStreamWriter |
将字符流转换为字节流(配合 Writer 使用) |
GZIPOutputStream |
用于压缩数据为 GZIP 格式 |
DeflaterOutputStream |
用于压缩 ZIP 或 Deflate 格式的数据 |
2.字符流顶层抽象类:
Reader
Writer
3.InputStream和OutputStream
1.InputStream 的基本作用
- 以字节为单位读取数据
- 支持顺序读取
- 提供基础 API,由子类实现具体功能
2. OutputStream的基本作用
- 以字节为单位写入数据
- 支持顺序写入
- 提供基础 API,由子类实现具体功能
3.InputStream和OutputStream 的核心方法
1.InputStream
方法 | 描述 |
---|---|
int read() |
从输入流中读取一个字节的数据,返回 0~255 的 int 值,若到达流末尾则返回 -1 |
int read(byte[] b) |
从输入流中读取最多 b.length 字节的数据到字节数组 b 中,返回实际读取的字节数 |
int read(byte[] b, int off, int len) |
从输入流中读取最多 len 字节的数据到字节数组 b 中,从偏移量 off 开始存放 |
void close() |
关闭输入流并释放相关资源(必须调用) |
long skip(long n) |
跳过 n 个字节,并返回实际跳过的字节数 |
int available() |
返回当前可读取的字节数(不阻塞) |
boolean markSupported() |
判断该流是否支持 mark() 和 reset() 操作 |
void mark(int readlimit) |
标记当前流的位置,后续可通过 reset() 回退 |
void reset() |
将流重置到最近一次调用 mark() 的位置 |
1. int read()
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
public class ReadSingleByte {
public static void main(String[] args) {
String filePath = "example.txt";
try (InputStream is = new FileInputStream(filePath)) {
int data;
while ((data = is.read()) != -1) {
System.out.print((char) data); // 转换为字符输出
}
}
catch (IOException e) { e.printStackTrace(); } } }
输出说明:
假设 example.txt
内容为:
Hello InputStream
输出结果:
Hello InputStream
2. int read(byte[] b)
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
public class ReadByteArray {
public static void main(String[] args) {
String filePath = "example.txt";
try (InputStream is = new FileInputStream(filePath)) {
byte[] buffer = new byte[1024]; // 缓冲区大小
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
System.out.write(buffer, 0, bytesRead);
}
}
catch (IOException e) { e.printStackTrace(); } } }
输出
与上一个例子相同,但效率更高,适合大文件读取。
3. int read(byte[] b, int off, int len)
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
public class ReadPartialArray {
public static void main(String[] args) {
String filePath = "example.txt";
try (InputStream is = new FileInputStream(filePath)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = is.read(buffer, 0, buffer.length)) != -1) {
System.out.println("Read " + bytesRead + " bytes: " + new String(buffer, 0, bytesRead));
}
}
catch (IOException e) { e.printStackTrace(); } } }
4. void close()
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
public class CloseStream {
public static void main(String[] args) {
String filePath = "example.txt";
try (InputStream is = new FileInputStream(filePath)) {
// 自动调用close()
int data;
while ((data = is.read()) != -1) {
System.out.print((char) data);
}
}
catch (IOException e) { e.printStackTrace(); } } }
5. long skip(long n)
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
public class SkipBytes {
public static void main(String[] args) {
String filePath = "example.txt";
try (InputStream is = new FileInputStream(filePath)) {
is.skip(5); // 跳过前5个字节
int data;
while ((data = is.read()) != -1) {
System.out.print((char) data);
}
}
catch (IOException e) { e.printStackTrace(); } } }
输出
若原文件是 "Hello InputStream"
,跳过后输出:
InputStream
6. int available()
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
public class AvailableBytes {
public static void main(String[] args) {
String filePath = "example.txt";
try (InputStream is = new FileInputStream(filePath)) {
System.out.println("Available bytes: " + is.available());
int data;
while ((data = is.read()) != -1) {
System.out.print((char) data);
}
}
catch (IOException e) { e.printStackTrace(); } } }
7. boolean markSupported()
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
public class MarkSupportedCheck {
public static void main(String[] args) {
String filePath = "example.txt";
try (InputStream is = new FileInputStream(filePath)) {
if (is.markSupported()) {
System.out.println("Mark/reset supported.");
}
else {
System.out.println("Mark/reset NOT supported.");
}
}
catch (IOException e) { e.printStackTrace(); } } }
8. void mark(int readlimit)
✅ 功能
在当前位置设置标记,之后可以调用 reset()
回退到该位置。
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
public class UseMarkAndReset {
public static void main(String[] args) {
String filePath = "example.txt";
try (InputStream is = new BufferedInputStream(new FileInputStream(filePath))) {
int firstChar = is.read();
System.out.println("First char: " + (char) firstChar);
is.mark(100); // 设置标记,允许最多读100字节后仍能回退
int secondChar = is.read();
System.out.println("Second char: " + (char) secondChar);
is.reset(); // 回退到mark的位置
int thirdChar = is.read();
System.out.println("Third char (after reset): " + (char) thirdChar); }
catch (IOException e) { e.printStackTrace(); } } }
输出
First char: H Second char: e Third char (after reset): H
2.OutputStream
方法 | 描述 |
---|---|
void write(int b) |
方法用于向输出流写入一个字节的数据。参数 outputStream.write(65); // 写入字符'A'的ASCII码 outputStream.write(66); // 写入字符'B'的ASCII码 |
void write(byte[] b) |
写入整个字节数组中的所有字节 |
void write(byte[] b, int off, int len) |
写入字节数组中从偏移量 off 开始的 len 个字节,
|
void flush() |
flush()方法用于强制刷新输出流缓冲区。当数据写入输出流时,通常会先存储在缓冲区中,以减少实际的物理写入次数,提高效率。但在某些情况下,需要确保缓冲区中的数据立即被写入目标,此时就需要调用flush()方法。例如,在网络通信中向客户端发送数据后,为保证数据及时发送,可调用flush(): |
void close() |
关闭输出流并释放相关资源(必须调用) |
1. void write(int b)
示例
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class WriteSingleByteExample {
public static void main(String[] args) {
String filePath = "single_byte_output.txt";
try (OutputStream os = new FileOutputStream(filePath)) {
int data = 'A'; // ASCII value of 'A' is 65
os.write(data); // 写入一个字节
}
catch (IOException e) { e.printStackTrace(); } } }
说明:
- 将字符
'A'
(ASCII 码为 65)作为一个字节写入文件single_byte_output.txt
。 - 文件内容将只包含一个字符:
A
。
2. void write(byte[] b)
示例
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class WriteByteArrayExample {
public static void main(String[] args) {
String filePath = "byte_array_output.txt";
try (OutputStream os = new FileOutputStream(filePath)) {
byte[] bytes = "Hello, OutputStream!".getBytes();
os.write(bytes); // 写入整个字节数组
}
catch (IOException e) { e.printStackTrace(); } } }
说明:
- 将字符串
"Hello, OutputStream!"
转换为字节数组后一次性写入文件。 - 文件内容为完整的字符串。
3. void write(byte[] b, int off, int len)
示例
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class WritePartialArrayExample {
public static void main(String[] args) {
String filePath = "partial_array_output.txt";
try (OutputStream os = new FileOutputStream(filePath)) {
byte[] bytes = "This is a test string.".getBytes();
os.write(bytes, 5, 10); // 从索引5开始写入10个字节
}
catch (IOException e) { e.printStackTrace(); } } }
说明:
- 原始字节数组对应字符串
"This is a test string."
。 - 从第 5 个字符(即
'i'
)开始写入 10 个字节,因此写入的内容是"is a test"
。
4. void flush()
示例
import java.io.FileOutputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class FlushExample {
public static void main(String[] args) {
String filePath = "flush_output.txt";
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(filePath))) {
byte[] bytes = "Data to be flushed".getBytes();
os.write(bytes); os.flush(); // 显式刷新缓冲区
} catch (IOException e) { e.printStackTrace(); } } }
说明:
- 使用了
BufferedOutputStream
,它内部有缓冲区。 - 调用
flush()
强制将缓冲区的数据写入目标文件。
5. void close()
示例
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class CloseExample {
public static void main(String[] args) {
String filePath = "close_output.txt";
try (OutputStream os = new FileOutputStream(filePath)) {
byte[] bytes = "Closing the stream properly".getBytes();
os.write(bytes);
}
catch (IOException e) { e.printStackTrace(); } } }
说明:
- 使用 try-with-resources 自动调用
close()
方法关闭流。 - 避免资源泄漏,确保流在使用完毕后被正确关闭。
4.常用使用示例
1.FileInputStream
和FileOutputStream
1.FileInputStream
创建 FileInputStream
对象,常用的2种方法:
new FileInputStream(File file):
通过 File
对象创建一个输入流
new FileInputStream(String name):
通过文件路径创建一个输入流。
其中 name
为文件的路径,如 d:\\0\\fist.txt
try (InputStream is = new FileInputStream("example.txt")) {
int data;
while ((data = is.read()) != -1) {
// // 读取的data是字节对应的编码值(int值),转换为字符后从编码获取对应的字符
System.out.print((char) data);
}
}
catch (IOException e) { e.printStackTrace(); }
/*
文件内容:
读取的 data=72,对应的字符 char=H
读取的 data=101,对应的字符 char=e
*/
2.FileOutputStream
常用构造方法:
FileOutputStream(String name)
:根据指定的文件名创建文件输出流,如果文件已存在,则覆盖原有内容;若文件不存在,将创建新文件。
FileOutputStream(File file)
:根据给定的File
对象创建文件输出流。如果文件已存在,则覆盖原有内容;若文件不存在,将创建新文件。若文件是目录或无法打开,抛出 FileNotFoundException
FileOutputStream(String name, boolean append)
:当append
为true
时,数据将追加到文件末尾;若为false
,则覆盖原有内容。
getBytes
方法通常指将输入流(如InputStream
)转换为字节数组(byte[]
)
try (FileOutputStream fos = new FileOutputStream("output.txt")) {
String text = "Hello, World!";
fos.write(text.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
2.BufferedInputStream和BufferedOutStream
一般我们是不会直接单独使用 FileInputStream ,通常会配合 BufferedInputStream来使用。这两个类是 Java I/O 包中用于提供缓冲功能的装饰流类。它们可以提高读取和写入数据的效率,减少底层 I/O 操作的次数。
1. BufferedInputStream
作用:为 InputStream
添加缓冲功能,从数据源读取数据时,先将数据批量读入缓冲区,后续的读取操作直接从缓冲区获取数据,减少了与数据源的交互次数。
常用构造函数:
BufferedInputStream(InputStream in)
:使用默认缓冲区大小(8192字节)创建缓冲流BufferedInputStream(InputStream in, int size)
:使用指定大小的缓冲区创建缓冲流
示例代码:
try (InputStream is = new BufferedInputStream(new FileInputStream("input.txt"))) {
int data;
while ((data = is.read()) != -1) {
System.out.print((char) data);
}
}
catch (IOException e) { e.printStackTrace(); }
import java.io.*;
public class BufferedInputStreamExample {
public static void main(String[] args) {
try (BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("large_file.dat"), 16384)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
// 处理读取的数据
processData(buffer, bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void processData(byte[] buffer, int bytesRead) {
// 数据处理逻辑
}
}
2. BufferedOutputStream
作用:为 OutputStream
添加缓冲功能,向目标写入数据时,先将数据写入缓冲区,当缓冲区满或调用flush()方法时,再将缓冲区中的数据批量写入目标,减少了与目标的交互次数。
常用构造函数:
BufferedOutputStream(OutputStream out)
:使用默认缓冲区大小(8192字节)创建缓冲流BufferedOutputStream(OutputStream out, int size)
:使用指定大小的缓冲区创建缓冲流
示例代码:
try (OutputStream os = new BufferedOutputStream(new FileOutputStream("output.txt"))) { String text = "Hello, World!";
os.write(
text.getBytes());
}
catch (IOException e) { e.printStackTrace(); }
import java.io.*;
public class BufferedOutputStreamExample {
public static void main(String[] args) {
try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("output.dat"), 16384)) {
byte[] data = generateData(1024 * 1024); // 生成1MB数据
// 写入数据到缓冲区
bos.write(data);
// 确保所有数据都写入底层流
bos.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
private static byte[] generateData(int size) {
byte[] data = new byte[size];
// 填充数据
for (int i = 0; i < size; i++) {
data[i] = (byte) (i % 256);
}
return data;
}
}
注意事项
- 缓冲机制:这两个类内部维护了一个内区区域的缓冲区(默认大小通常是 8KB),减少了直接与底层数据源(如磁盘、网络)的交互次数,从而提高了IO操作的效率。
- 关闭流:使用完后务必关闭流以释放资源,并确保所有数据被正确写入目标输出流。
- 线程安全:它们不是线程安全的,多线程环境下需要额外同步控制。