- 在Java API中,可以从其中读入一个字节序列的对象称做输入流,而可以向其中写入一个字节序列的对象称做输出流。这些字节序列的来源地和目的地可以是文件,而且通常都是文件,但是也可以是网络连接,甚至是内存块。抽象类
InputStream
和OutputStream
构成了输入/输出(I/O)类层次结构的基础。 - 因为面向字节的流不便于处理以
Unicode
形式存储的信息,所以从抽象类Reader
和Writer
中继承出来了一个专门用于处理Unicode
字符的单独的类层次结构。这些类拥有的读入和写出操作都是基于两个字节的Char值的(即,Unicode码元),而不是基于byte值的。
一.读写字节
InputStream
类有一个抽象方法:abstract int read()
这个方法读入一个字节,并返回读入的字节,或者在遇到输入源结尾时返回-1。在设计具体的输入流类时,必须覆盖这个方法以提供适用的功能,例如,在FileInputStream
类中,这个方法将从某个文件中读入一个字节,而System.in
(它是InputStream
的一个子类的预定义对象)却是从标准输入中读入信息,即控制台或重定向的文件。
System.in 标准”输入流。 这个流已经存在打开并准备提供输入数据。 通常这个流对应于键盘输入或指定的其他输入源主机环境或用户。
InputStream
类还有若干个非抽象的方法,它们可以读入一个字节数组,或者跳过大量的字节。这些方法都要调用抽象的read方法,因此,各个子类都只需覆盖这一方法。- 与此类似,
OutputStream
类定义了abstract void write(int b)
它可以向某个输出位置写出一个字节。read
和write
方法在执行时都将阻塞。这使得在这两个方法等待指定的流变为可用的这段时间里,其他的线程就有机会去执行有用的工作。 available
方法使我们可以去检查当前可读入的字节数量,并且保证代码不被阻塞。
try(FileInputStream in = new FileInputStream("C:\\Users\\whz\\Desktop\\岗位要求.txt")) {
int bytesAvailable = in.available();
if(bytesAvailable > 0){
byte[] data = new byte[bytesAvailable];
in.read(data);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
- 当完成对输入/输出流的读写时,应该通过调用close方法来关闭它,这个调用会释放十分有限的操作系统资源。如果一个应用程序打开了过多的输入/输出流而没有关闭,那么系统资源将被耗尽。关闭一个输出流的同时还会冲刷用于该输出流的缓冲区:所有被临时置于缓冲区中,以便用更大的包的形式传递的字节在关闭输出流时都将被送出。特别是,如果不关闭文件,那么写出字节的最后一个包可能将永远也得不到传递。当然,我们还可以用flush方法来认为地冲刷这些输出。
- 即使某个输入/输出流类提供了适用原生的read和write功能的某些具体方法,应用系统的程序员还是很少使用它们,因为大家感兴趣的数据可能包含数字、字符串和对象,而不是原生字节。
- 我们可以使用众多的从基本的
InputStream
和OutputStream
类导出的某个输入/输出类,而不只是直接使用字节。
二.完整的流家族
- 把输入/输出流家族中的成员按照它们的使用方法来进行划分,这样就形成了处理字节和字符的两个单独的层次结构。
- 首先来看
InputStream
和OutputStream
的层次结构。
- 要想读写字符串和数字,就需要功能更加强大的子类,例如,
DataInputStream
和DataOutputStream
可以以二进制格式读写所有的基本Java类型。最后,还包含了多个有用的输入/输出流,例如,ZipInputStream
和ZipInputStream
可以以常见的ZIP压缩格式读写文件。 - 另一方面,对于Unicode文本,可以使用抽象类Reader和Writer的子类。
Reader
和Writer
类的基本方法与InputStream
和OutputStream
中的方法类似
abstract int read()
abstract void writer(int c)
read
方法将返回一个Unicode码元(一个在 0 ~ 65535 之间的整数),或者在碰到文件结尾时返回-1。writer
方法在被调用时,需要传递一个Unicode码元。
三.组合输入/输出流过滤器
FileInputStream
和FileOutputStream
可以提供附着在一个磁盘文件上的输入流和输出流,我们只需向其构造器提供文件名或文件的完整路径名。例如:
FileInputStream fin = new FileInputStream("C:\\Users\\whz\\Desktop\\岗位要求.txt");
- 这里使用绝对路径的写法,读取文件。还可以使用相对路径的写法,通过
System.getProperty("user.dir");
获取当前用户工作目录。 - 考录到不同系统的兼容性,文件路径中的分割符号可以使用
File.separator
来替换。如
FileInputStream fin = new FileInputStream("C:"+File.separator+"Users"
+File.separator+"whz"+File.separator
+"Desktop"+File.separator+"岗位要求.txt");
- 如果只有
DataInputStream
,那么就只能读入数值类型,如:
DataInputStream din = ...;
double x = din.readDouble();
- 但是正如
FileInputStream
没有任何读入数值类型的方法一样,DataInputStream
也没有任何从文件中读取数据的方法。 - Java使用了一种灵巧的机制来分离这两种职责。某些输入流(例如
FileInputStream
和由URL
类的openStream
方法返回的输入流)可以从文件和其他更外部的位置上获取字节,而其它的输入流(例如DataInputStream
)可以将字节组装到更有用的数据类型中。Java程序员必须对二者进行组合。例如,为了从文件中读入数字,首先需要创建一个FileInputStream
,然后将其传递给DataInputStream
的构造器。
DataInputStream din = new DataInputStream(
new FileInputStream("C:\\Users\\whz\\Desktop\\岗位要求.txt"));
char x = din.readChar();
- 在
InputStream
/OutputStream
结构图中有FilterInputStream
和FilterOutStream
类。这些文件的子类用于向处理字节的输入/输出流添加额外的功能。 - 可以通过嵌套过滤器来添加多重功能。例如,输入流在默认情况下是不被缓冲区缓存的,也就是说,每个对read的调用都会请求操作系统再分发一个字节。相比之下,请求一个数据块并将置于缓冲区中会显得更加高效。如果想使用缓冲机制,以及用于文件的数据输入方法,那么就需要:
DataInputStream din = new DataInputStream(
new BufferedInputStream(
new FileInputStream("C:\\Users\\whz\\Desktop\\岗位要求.txt")));
- 注意,这里把
DataInputStream
置于构造器链的最后,这是因为我们希望使用DataInputStream
的方法,并且希望它们能够使用带缓冲机制的read方法。 - 有时当多个输入流链接在一起时,需要跟踪各个中介输入流。例如,当读入输入时,经常需要预览下一个字节,以了解它是否是你想要的值。Java提供了用于此目的的
PushbackInputStream
:
PushbackInputStream pbin = new PushbackInputStream(
new BufferedInputStream(
new FileInputStream("C:\\Users\\whz\\Desktop\\岗位要求.txt")));
- 现在可以预读下一个字节:
int b = pbin.read();
- 并且在它并非你所期望的值时将其推回流中。
pbin.unread(b);
- 但是读入和推回是可应用于可回推输入流的仅有的方法。入股希望能够预先浏览并且还可以读入数字,那么你就需要一个既是可回推输入流,又是一个数据输入流的引用。
PushbackInputStream pbin = null;
BufferedInputStream bis = null;
DataInputStream din = new DataInputStream(
pbin = new PushbackInputStream(
bis= new BufferedInputStream(
new FileInputStream("C:\\Users\\whz\\Desktop\\岗位要求.txt"))));