一、定义
Java 的输入输出流(I/O流)是用于处理输入和输出操作的一组类和接口。Java I/O 流的主要功能是读取和写入数据,通常是从文件、网络连接、控制台等各种输入输出设备或介质中
二、体系结构
1、字节流(Byte Stream)
主要用于处理二进制数据。
2、字符流(Character Stream)
主要用于处理文本数据。
三、介绍
1、字节流
InputStream
和 OutputStream
是字节流的两个抽象超类。它们分别用于处理字节的输入和输出操作。因为它们是抽象类,所以都包含了一些抽象方法,这些方法需要在具体的子类中进行实现
InputStream抽象方法:
1.、`int read()`:核心方法,它用于读取一个字节并返回读到的字节(范围是 `0` 到 `255` 的整数值)。如果到达流的末尾,则返回 `-1`.。每次
调用read()方法后,文件指针向前就会向文章末尾方向移动一个字节,以便下次读取新的字节
2. `int read(byte[] b, int off, int len)`:将字节读取到指定的字节数组中,从指定的偏移量开始填充数组,最多读取 `len` 个字节。返回实际读取的字节数,或在到达流的末尾时返回 `-1`。有助于减少系统调用,便于批量处理,提高了读取效率
3. `long skip(long n)`:跳过和丢弃输入流中的 `n` 个字节,返回实际跳过的字节数
4. `int available()`:返回可以不受阻塞地读取的字节数
5. `void close()`:关闭此输入流并释放与该流关联的所有系统资源
6. `void mark(int readlimit)`:在输入流的当前位置标记位置。将来可以通过调用 `reset()` 方法返回到此位置
7. `void reset()`:将流重新定位到最后标记的位置
8. `boolean markSupported():测试此输入流是否支持 `mark` 和 `reset` 方法
OutputStream抽象方法:
1. `void write(int b)`:核心方法,将指定的字节写入输出流
2. `void write(byte[] b, int off, int len)`:将指定字节数组中从偏移量 `off` 开始的 `len` 个字节写入输出流
3. `void flush()`:刷新该输出流并强制写出所有缓冲的输出字节
4. `void close()`:关闭该输出流并释放与该流关联的所有系统资源
1.1 基本流
1、FileInputStream & FileOutputStream:用于
从文件读取和写入字节数据
使用:
1、创建FileInputStream流对象
eg:FileInputStream fileInputStream = new FileInputStream(filePath);
2、读取数据的主要方法
- read():
可以一次读取一个字节
- read(byte[] b)
:一次读取多个字节,存入字节数组 b
中
- read(byte[] b, int off, int len):
从输入流中读取最多 len
个字节的数据,并将其存储到缓冲区数组 b
中,从偏移量 off
开始存储,返回实际读取的字节数
1、创建FileOutputStream流对象
eg:FileOutputStream fos = new FileOutputStream(filePath, true); true代表追加数据而不是覆盖它,可不写,默认值为false覆盖
2、写入数据的主要方法
- write(int b): 将指定的一个字节数据写入输出流
- write(byte[] b):将整个字节数组 b
写入输出流
- write(byte[] b, int off, int len):将指定字节数组 b
中从偏移量 off
开始的 len
个字节写入输出流。
2、ByteArrayInputStream & ByteArrayOutputStream:用于在内存中操作字节数组的数据流
使用:
1、创建ByteArrayInputStream对象
byte[] data = "Hello, World!".getBytes();
eg:
ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
2、读取数据的主要方法
- `read()`: 从输入流中读取下一个字节的数据。
- `read(byte[] b, int off, int len)`: 从输入流中最多读取 `len` 个字节,并将它们存储到字节数组 `b` 中,从偏移量 `off` 开始存储。
- `reset()`: 将流重新定位到字节数组的开始处。
- `available()`: 返回可以读取的字节数。
1、创建ByteArrayOutputStream对象
eg:ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
2、写入数据的主要方法
- `write(int b)`: 将指定的字节写入此输出流。
- `write(byte[] b, int off, int len)`: 将指定字节数组中从偏移量 `off` 开始的 `len` 个字节写入此输出流。
- `toByteArray()`: 返回此输出流的当前内容作为一个字节数组。
- `reset()`: 将该流的计数器重置,以便重新使用输出流中的缓冲区。
3、PipedInputStream & PipedOutputStream:用于线程之间字节数据传输(即通过一个线程将数据写入 PipedOutputStream
,另一个线程可以从对应的 PipedInputStream
中读取这些数据)
使用:
1、创建连接的两个管道流对象(构造函数或或 connect方法的方式)
eg:PipedOutputStream outputStream = new PipedOutputStream();
PipedInputStream inputStream = new PipedInputStream(outputStream);
2、写入和读取数据的主要方法
在写入线程中,通过 PipedOutputStream
的 write()
方法向管道中写入数据
在读取线程中,通过 PipedInputStream
的 read()
方法从管道中读取数据
注意:
管道流必须是连接状态才能使用,通常是通过构造函数或者 connect()
方法连接;
管道流主要用于线程之间的数据传输,不适合在同一线程内使用,否则会导致死锁;
读取线程必须在写入线程之后执行 read()
操作,否则会阻塞,直到有数据可读
4、SequenceInputStream:将多个 InputStream
串联成一个单一的流,读取时依次读取
使用:
1、创建对象
eg:SequenceInputStream(InputStream s1, InputStream s2) 或者
// 创建多个文件输入流
FileInputStream fileInputStream1 = new FileInputStream("file1.txt");
FileInputStream fileInputStream2 = new FileInputStream("file2.txt");
...
// 将这些流添加到 Vector 中 Vector<InputStream> streams = new Vector<>(); streams.add(fileInputStream1); streams.add(fileInputStream2); ...
// 创建一个 Enumeration
Enumeration<InputStream> e = streams.elements();
// 使用 SequenceInputStream 将这些文件流串联
SequenceInputStream sequenceInputStream = new SequenceInputStream(e);
2、读取数据的主要方法
从 SequenceInputStream 使用read()依次读取数据
1.2 过滤流
通过装饰设计模式等增强基本流的功能。
1、BufferedInputStream & BufferedOutputStream:字节流的高级包装类,用于提高 I/O 操作的效率
使用:
1、创建BufferedInputStream对象
eg:FileInputStream fis = new FileInputStream(inputFile);
BufferedInputStream bis = new BufferedInputStream(fis);
2、读取数据的主要方法
通过 read()
方法从内部缓冲区读取数据,如果缓冲区空了,它会一次性读取较大块的数据填充缓冲区
1、创建BufferedOutputStream对象
eg:FileOutputStream fos = new FileOutputStream(outputFile);
BufferedOutputStream bos = new BufferedOutputStream(fos);
2、写入数据的主要方法
通过 write()
方法将数据写入内部缓冲区,只有当缓冲区满了或者手动调用 flush()
方法时,数据才会真正写入文件
2、DataInputStream & DataOutputStream:用于以机器无关的方式读取和写入基本数据类型(如 int
、long
、float
、double
等)的数据
使用:
1、创建DataInputStream对象
eg:DataInputStream dis = new DataInputStream(new FileInputStream("data.bin")
2、读取数据的主要方法
- `readInt()`:读取 4 字节的整数。
- `readDouble()`:读取 8 字节的双精度浮点数。
- `readUTF()`:读取使用 UTF-8 编码的字符串。
1、创建DataOutputStream对象
eg:DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.bin")
2、写入数据的主要方法
- `writeInt(int v)`:写入 4 字节的整数。
- `writeDouble(double v)`:写入 8 字节的双精度浮点数。
- `writeUTF(String str)`:以 UTF-8 编码写入字符串。
3、ObjectInputStream & ObjectOutputStream:用于对象序列化和反序列化,处理对象的输入和输出,允许程序将对象写入流中或从流中读取对象
使用:
1、创建ObjectInputStream对象
eg:FileInputStream fis = new FileInputStream("object.dat");
ObjectInputStream ois = new ObjectInputStream(fis)
2、从输入流中读取对象(反序列化)的主要方法
-readObject():从 ObjectInputStream
中读取对象,并将其反序列化为一个 Java 对象。需要将读取的对象强制转换为适当的类型
eg:MyObject obj = (MyObject) ois.readObject();
-readInt()`、`readBoolean()`、`readChar()` 等:用于读取基本数据类型的值,这些值通常是由对应的 ObjectOutputStream
方法写入的
1、创建ObjectOutputStream对象
eg:FileOutputStream fos = new FileOutputStream("object.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos)
2、将对象写入到输出流(序列化)的主要方法
-writeObject(Object obj):将指定的对象写入到 ObjectOutputStream
中。对象必须实现 Serializable
接口。
eg:oos.writeObject(someObject);
-writeInt(int val)`、`writeBoolean(boolean val)`、`writeChar(int val)` 等:用于写入基本数据类型的值,作为对象数据的补充。它们分别写入整型、布尔型、字符型等
4、PushBackInputStream:将读取的数据回推到流中。对于需要预读字节数据、检查数据类型或需要在读取后可能返回的数据场景非常有用
使用:
1、创建PushBackInputStream对象
eg:PushBackInputStream pbis = new PushBackInputStream(new ByteArrayInputStream(bytes))
或者 PushbackInputStream in = new PushbackInputStream(InputStream in, int size);size
: 推回缓冲区的大小,单位是字节。
比如:PushbackInputStream in = new PushbackInputStream(new ByteArrayInputStream(new byte[]{65, 66, 67, 68}), 2);指定了缓冲区大小会为2个字节
退回俩字节:
int b1 = in.read(); // 读取字节 65 ('A') int b2 = in.read(); // 读取字节 66 ('B') // 推回两个字节 in.unread(b2); // 推回字节 66 ('B') in.unread(b1); // 推回字节 65 ('A')
当回推第三个字节则会报错IOException
2、回推读取的字节数据的主要方法
-unread(int b):将一个字节 `b` 回推到输入流中。下次调用 `read()` 时,将首先读取这个回推的字节
-unread(byte[] b, int off, int len):将字节数组 `b` 中从 `off` 位置开始的 `len` 个字节回推到输入流中。下次调用 `read()` 时,将首先读取这些回推的字节
1.3 自定义
通过继承InputStream
或OutputStream
类来自定义输入或输出流的行为,比如创建一个类并继承自 InputStream
或 OutputStream
,然后覆盖其中的抽象方法,如 read()
或 write()
方法
作用:
1. 数据过滤:可以通过自定义流在数据读取或写入的过程中添加过滤器,例如压缩、解压缩、加密、解密等。
2. 数据转换:可以在数据流中自动进行某些转换操作,例如将大写字母转换为小写,或将字节数组转换为其他格式。
3. 数据拦截:可以在流的处理中插入拦截操作,进行数据的监控或记录日志。
2、字符流
Reader
和 Writer
是字符流的两个抽象超类。它们分别用于处理符的读取和写入操作。因为它们是抽象类,所以都包含了一些抽象方法,这些方法需要在具体的子类中进行实现
Reader抽象方法:
1. `int read()`:读取单个字符并返回。如果已经到达流的末尾,则返回 -1。
2. `int read(char[] cbuf, int off, int len)`:读取字符并将其存储到字符数组中。`off` 是起始偏移量,`len` 是读取的最大字符数。返回实际读取的字符数,如果到达末尾,返回 -1。
3. `void close()`:关闭流并释放与流相关的资源。
Writer抽象方法:
1. `void write(int c)`:写入单个字符。
2. `void write(char[] cbuf, int off, int len)`:将字符数组中的一部分字符写入输出流。`off` 是起始偏移量,`len` 是写入的字符数。
3. `void flush()`:刷新流,强制将缓冲区的内容写入到目标设备。
4. `void close()`:关闭流并释放与流相关的资源。
2.1 基本流
1、FileReader & FileWriter:用于读取和写入文本文件
使用:
1、创建FileReader对象
eg:FileReader reader = new FileReader("example.txt")
2、读取数据的主要方法
-`read()`:读取单个字符并返回其对应的 Unicode 编码值。如果已到达文件末尾,则返回 `-1`。
-`read(char[] cbuf, int offset, int length)`:将字符读入数组 `cbuf` 中,从偏移量 `offset` 开始,最多读取 `length` 个字符。返回实际读取的字符数,如果已到达文件末尾,则返回 `-1`。
-`ready()`:检查流是否准备好被读取。如果流在关闭前有字符可供读取,则返回 `true`,否则返回 `false`。
1、创建FileWriter对象
eg:FileWriter writer = new FileWriter("example.txt")
2、写入数据的主要方法
- `void write(int c)`:将指定的字符写入到文件中。参数 `c` 是要写入的字符的 Unicode 编码值。
- `void write(char[] cbuf, int offset, int length)`:将指定的字符数组的一部分写入文件。`cbuf` 是要写入的字符数组,`offset` 是起始偏移量,`length` 是要写入的字符数。
- `void write(String str)`:将指定的字符串写入文件。
2、CharArrayReader & CharArrayWriter:用于从字符数组中读取数据和向字符数组中写入数据
使用:
1、创建CharArrayReader对象
eg:char[] charArray = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'};
CharArrayReader charArrayReader = new CharArrayReader(charArray)
2、读取字符数据的主要方法
-`int read()` :读取单个字符,并返回该字符的整数表示。如果到达流的末尾,则返回 -1。
-`int read(char[] b, int off, int len)` :将字符读入指定的数组 `b` 中,从数组的 `off` 偏移量开始,最多读取 `len` 个字符。返回实际读取的字符数,如果到达流的末尾则返回 -1。
-`boolean ready()`:判断流是否准备好被读取。如果 `ready()` 返回 `true`,则意味着后续的 `read()` 调用不会阻塞。
-`long skip(long n)`:跳过 `n` 个字符,不读取这些字符。返回实际跳过的字符数。
-`void close()`:关闭流。关闭后,不能再读取该流中的字符。
-`boolean markSupported()`:判断当前流是否支持 `mark()` 和 `reset()` 方法。对于 `CharArrayReader`,总是返回 `true`。
-`void mark(int readAheadLimit)` :在流中标记当前位置。随后可以通过 `reset()` 方法返回到标记的位置。
-`void reset()`:将流重置到最后标记的位置。可以多次调用 `reset()` 返回到相同的标记点。
1、创建CharArrayWriter对象
eg:CharArrayWriter charArrayWriter = new CharArrayWriter();
2、写入字符数据的主要方法
-`void write(int c)`:将单个字符写入字符数组。
-`void write(char[] c, int off, int len)`:将指定的字符数组 `c` 中从 `off` 偏移量开始的 `len` 个字符写入字符数组。
-`void write(String str)`:将一个字符串写入字符数组。
-`void writeTo(Writer out)`:将当前 `CharArrayWriter` 中的内容写入到另一个字符输出流中。
-`CharArrayWriter append(CharSequence csq)`:将指定的字符序列追加到当前字符数组中。返回当前的 `CharArrayWriter` 对象,以便支持链式调用。
-`CharArrayWriter append(CharSequence csq, int start, int end)` :将指定字符序列的一个子序列追加到当前字符数组中。返回当前的 `CharArrayWriter` 对象,以便支持链式调用。
-`CharArrayWriter append(char c)`:将单个字符追加到当前字符数组中。返回当前的 `CharArrayWriter` 对象,以便支持链式调用。
-`void reset()`: 清空当前 `CharArrayWriter` 中的缓冲区,使其可以重新使用。
-`char[] toCharArray()`:返回当前缓冲区中的字符数组的副本。
-`int size()` :返回当前缓冲区中字符的数量。
-`String toString()`:返回当前缓冲区内容的字符串表示形式。
-`void close()` :关闭流。`CharArrayWriter` 的 `close()` 方法不会实际关闭流,因为它只是清空缓冲区的内容,但不会影响其继续使用。
3、StringReader & StringWriter:在内存中处理字符串数据
使用:
1、创建StringReader对象
eg:String data = "Hello, World!";
StringReader stringReader = new StringReader(data);
2、读取字符串主要方法
- `int read()`:从 `StringReader` 中读取单个字符,并返回对应的字符的整数值。如果到达字符串的末尾,则返回 `-1`。
- `int read(char[] cbuf, int offset, int length)`:将字符读入到一个字符数组 `cbuf` 中,从指定的 `offset` 位置开始,最多读取 `length` 个字符。返回实际读取的字符数,如果到达字符串末尾,则返回 `-1`。
- `long skip(long n)`:跳过并丢弃 `n` 个字符,返回实际跳过的字符数。
- `boolean ready()`:判断流是否准备好被读取。如果可以读取,则返回 `true`,否则返回 `false`。
- `void close()`:关闭 `StringReader`,释放相关资源。关闭后,不能再从此流中读取数据。
1、创建StringWriter对象
eg:StringWriter stringWriter = new StringWriter();
2、将字符数据写入一个内部的字符串缓冲区的主要方法
- `void write(int c)`:将单个字符写入 `StringWriter` 的缓冲区中。
- `void write(char[] cbuf, int offset, int length)`:将字符数组 `cbuf` 中从 `offset` 位置开始的 `length` 个字符写入到 `StringWriter` 中。
- `void write(String str)`:将一个字符串 `str` 写入到 `StringWriter` 中。
- `String toString()`:返回缓冲区中当前的字符串内容。这是 `StringWriter` 最常用的方法,用来获取所有写入的数据。
- `void flush()`:刷新流,这里 `StringWriter` 由于是写入到内存中的字符串缓冲区,所以 `flush()` 通常不需要显式调用。
- `void close()`:关闭 `StringWriter`,但实际上对 `StringWriter` 没有实际影响,通常可以忽略该操作。
3、PipedReader & PipedWriter:用于线程之间字节数据传输(即通过一个线程将数据写入 PipedWriter
,另一个线程可以从对应的 PipedReader
中读取这些数据)
使用:
1、创建连接的两个管道流对象(构造函数或或 connect方法的方式)
eg:PipedReader reader = new PipedReader();
PipedWriter writer = new PipedWriter();
// 连接PipedReader和PipedWriter
writer.connect(reader);
2、读取数据的主要方法
-read():从管道中读取一个字符。如果没有数据可读取,当前线程将阻塞,直到有数据可读或管道关闭
- read(char[] cbuf, int off, int len):将最多 `len` 个字符的数据读入字符数组 `cbuf` 中,从偏移量 `off` 开始填充。如果没有足够的数据可读,则会阻塞,直到有足够数据或管道关闭。
3、写入数据的主要方法
-write(int c):将一个字符写入到管道。如果管道没有连接,或者如果管道的缓冲区已满,当前线程将阻塞,直到可以继续写入
-write(char[] cbuf, int off, int len):将字符数组 `cbuf` 中的 `len` 个字符从偏移量 `off` 开始写入到管道中。如果管道缓冲区已满,当前线程将阻塞,直到可以继续写入
-flush():刷新该流并强制写出所有缓冲的输出字符。通常不需要显式调用 `flush()`,因为在写入完毕后通常会调用 `close()`,这也会刷新缓冲区
2.2 过滤流
通过装饰设计模式等增强基本流的功能。
1、 BufferedReader & BufferedWriter:字符流的高级包装类,用于提高 I/O 操作的效率
使用:
1、创建BufferedReader对象
eg:BufferedReader br = new BufferedReader(new FileReader("example.txt"))
2、从字符输入流读取字符的主要方法
-read()
:一次读取一个字符
-readLine():读取一行文本,返回一个字符串(不包含行终止符)。如果已经达到流的末尾,则返回 null
。
1、创建BufferedWriter对象
eg:BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))
2、将字符写入到字符输出流的主要方法
-write(String str)
:将指定的字符串写入字符输出流
-newLine()
:写入一个行终止符,跨平台兼容(不同的系统可能有不同的行终止符)。
-flush()
:将缓冲区中的内容写入目标流中,确保数据被写出
2、LineNumberReader:用于处理带行号的文本数据
使用:
1、创建LineNumberReader对象
eg:LineNumberReader lineNumberReader = new LineNumberReader(new FileReader("example.txt"))
2、读取文本文件并获取当前读取行的行号的主要方法
-getLineNumber()
:返回当前的行号。
-setLineNumber(int lineNumber)
:设置行号。
-readLine()
:读取一行文本。
3、PrintWriter:是一个字符输出流,它提供了打印各种数据表示形式的方法
使用:
1、创建PrintWriter对象
eg:PrintWriter writer = new PrintWriter("output.txt",true);
true参数表示自动刷新(即在调用 println
或 printf
后自动调用 flush()
方法)
2、打印数据的主要方法
-print():将各种数据类型直接写入输出流而不附加任何行分隔符
-println():与 print()
方法类似,但在输出数据后会自动附加一个行分隔符(通常是换行符 \n
)
-format()
和 printf():
用于格式化输出。这两个方法的作用相同
eg:printWriter.printf("Name: %s, Age: %d", "Alice", 30); // 输出 "Name: Alice, Age: 30"
-flush():强制将缓冲区中的数据写入到目标输出流。如果你在写入数据后没有调用 flush()
,数据可能不会立即写入目标(尤其是在使用缓冲输出流时)
-write():与 print()
和 println()
方法类似(但 write() 方法不会将数据转换为字符串,也不会附加行分隔符),于直接写入字符或字符数组到输出流,
4、PushBackReader:允许在读取字符后将它们推回到流中
使用:
1、创建PushBackReader对象
eg:PushbackReader pushbackReader = new PushbackReader(new StringReader(str)) 或者
PushbackReader reader = new PushbackReader(Reader in, int size);size
: 是推回缓冲区的大小。默认大小是1,但你可以指定更大的值,以推回多个字符
2、回推读取的字符数据的主要方法
-unread(int c):将一个字符 c
推回到流中,使得下次读取时首先读取这个字符。如果推回的字符数超过了缓冲区的大小(构造方法中的size),会抛出 IOException
-unread(char[] cbuf, int off, int len):将字符数组 cbuf
中从偏移量 off
开始的 len
个字符推回到流中。如果推回的字符数超过了缓冲区的大小(构造方法中的size),会抛出 IOException
2.3 自定义
通过继承Reader
或Writer
类来自定义输入或输出流的行为,比如创建一个类并继承自 Reader
或Writer
,然后覆盖其中的抽象方法,如 read()
或 write()
方法
作用:
1. 数据过滤:可以通过自定义流在数据读取或写入的过程中添加过滤器,例如压缩、解压缩、加密、解密等。
2. 数据转换:可以在数据流中自动进行某些转换操作,例如将大写字母转换为小写,或将字节数组转换为其他格式。
3. 数据拦截:可以在流的处理中插入拦截操作,进行数据的监控或记录日志。