一、概述
Java 类库定义了许多类专门负责各种方式的输入/输出,这些类都被放在 java.io 包中。它具有4个最基本的类,如下所示:
- InputStream:字节输入流,它的子类都含有
read()
方法用于读入字节数据。 - OutputStream:字节输出流,它的子类都含有
write()
方法用于写出字节数据。 - Reader:字符输入流,它的子类都含有
read()
方法用于读入字符数据。 - Writer:字符输出流,它的子类都含有
write()
方法用于写出字符数据。
这 4 个类均为抽象类,它们各自都有许多继承自它们的子类用以提供不同的功能,在使用过程中我们往往借助多态的特性来使用这些类及其它们的子类。
在 Java 中,对于待处理的数据我们将其抽象成了流(Stream),它屏蔽了实际的 I/O 设备中处理数据的细节,对于我们用户来说,就好像一条水流流入(InputStream)或流出(OutputStream)程序,这样就能使得我们更加方便地处理数据。
接下来我们就来看到这些类的继承体系结构,如下图所示:
其中红色字体的类是在日常开发中使用较为频繁的类,也是我们接下来会介绍的类。除了前面所说的四个类外,我们还注意到一个类 RandomAccessFile
,这个类独立于这 4 个类之外,提供了随机访问文件的方法,后面我们也会对其进行介绍。
需要特别注意一点的是:虽然上图所展示出来的类特别多,但是并不复杂,继承自同一父类的方法,它们的使用大多是大同小异的,所以只要学习了其使用的方式和思想,对于未学过的子类只需要翻阅官方文档也能快速上手。
二、InputStream & OutputStream
接下来我们就先从整体上来了解下两个基本的抽象类:InputStream
和 OutputStream
。它们分别用于处理输入字节流和输出字节流,首先来看到 InputStream
及其提供的基本方法:
1. InputStream
InputStream
用于处理字节输入流,它主要提供了以下核心方法,如下表所示:
方法 | 说明 |
---|---|
read() | 从输入流读取数据的下一个字节,它的返回值即下一个字节的值,如果不存在下一个字节则返回-1 |
read(byte[] b) | 从输入流读取一些字节数,并将它们存储到缓冲区b。它的返回值为读取的输入流字节数长度,如果已到达文件末尾则返回-1 |
read(byte[] b, int off, int len) | 带有偏移量off和读取长度len的读取字节数的方法,数据会存储到缓冲区b,返回值为读取的字节数长度 |
close() | 关闭此输入流并释放与此流相关联的任何系统资源。 |
这三个方法读取字节的方法我们可以用搬家的例子来类比:
read()
方法就是蚂蚁搬家的方式,一个字节一个字节地读取。read(byte[] b)
和read(byte[] b, int off, int len)
方法就是卡车搬家的方式,将字节按数组的方式一组一组地读入。
接下来我们通过 3 个例子来展示这三个方法的使用:
read()
在这里及下面的例子中,我们会使用 FileInputStream
类来读入文件的数据。它是 InputStream
的子类,专门用于从文件中读入数据,后面我们会详细介绍它的使用,这里先了解即可。然后我们在电脑的桌面上创建一个 demo.txt
的文本文件,如下所示:
接着我们通过 Java 代码将其数据读入,打印输出,代码如下所示:
public class IODemo01 {
public static void main(String[] args) throws IOException {
File file = new File("C:/Users/Marck/Desktop/demo.txt");
InputStream in = new FileInputStream(file);
// 读取的数据
int b = -1;
// 循环读取数据,直到到达文件末尾
while ((b = in.read()) != -1){
System.out.println((char) b);
}
}
}
需要注意的是 FileInputStream
的构造方法以及 read
方法均会抛出异常,这里我们将其直接抛给 main
方法来处理。输出结果如下所示:
T
o
b
e
o
r
n
o
t
t
o
b
e
可以看到确实是一个字节一个字节地输出的,在程序中我们将接收到的字节数直接强转成了字符输出。注意空格也是算作一个字符的。
read(byte[] b)
read(byte[] b)
方法我们前面说过是卡车搬家的方式,我们需要在程序中预先设置一个缓冲字节数组组,数组的长度就是缓冲区的大小。当缓冲区大于输入流长度时,输入流就相当于一次性被读入了字节数组;否则的话输入流就会被分段读入,方法的返回值即为读入的字节长度。当到达文件末尾时,返回值为-1。示例代码如下所示:
public class IODemo01 {
public static void main(String[] args) throws IOException {
File file = new File("C:/Users/Marck/Desktop/demo.txt");
InputStream in = new FileInputStream(file);
// 设置缓冲区大小为 4byte
byte[] buffer = new byte[4];
int len = -1;
while ((len = in.read(buffer)) != -1){
System.out.println(new String(buffer));
}
}
}
运行结果如下:
To b
e or
not
to
be
由于我们设置的缓冲区长度为4个字节,所以每次循环都会读出4个字节的数据存储到 buffer
数组中。在最后一次读取时,只剩下两个字节的数据未读取,所以 buffer
只有前两个元素是有读取的数据的。
read(byte[] b, int off, int len)
该 read()
方法同样也是读取一组字节数缓存到 b 中,不同的是它可以指定偏移值 off 和 读取的长度 len。其中 off 为读入的数据在 b 中的起始偏移量,而 len 则是每次指定读入的字节数大小,示例代码如下所示:
public class IODemo01 {
public static void main(String[] args) throws IOException {
File file = new File("C:/Users/Marck/Desktop/demo.txt");
InputStream in = new