一、Java 输入与输出(I\O)系统概述
Java 输入/输出流(Input/Output,简称I/O)是Java语言用于读写数据的API,它提供了一系列类和接口,用于读取和写入各种类型的数据信息。
输入/输出(I/O)流:是一组有序的数据结构,它是有顺序的,有起点和终点的字节集合,是对数据传输的抽象。它将数据从一个地方带到另一个地方。
Java I/O的层次结构概述
Java的I/O流可分成低级节点流和高级的处理流。Java通过装饰器设计模式来对I/O流进行组合,可以方便地提供了缓冲、过滤、对象序列化等特殊功能,以满足各种复杂的I/O需求。Java I/O的层次结构大致可以分为以下几个层次:
最底层:基于本地操作系统的I/O操作,这是所有I/O操作的基础。Java的I/O操作最终都会转化为底层的操作系统的系统调用,以实现数据的读写功能。
抽象层:通过InputStream、OutputStream、Reader和Writer等抽象类,Java为I/O操作提供了统一的接口。这些接口屏蔽了底层操作系统的差异,使得Java程序可以在不同的操作系统上运行而无需修改。这些抽象类定义了数据读写的基本方法,如read()和write(),以及其他一些辅助方法。
装饰器层:例如,通过BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter等装饰器类,Java为IO操作提供了缓冲功能。缓冲流通过内存缓冲区来存储数据,从而减少了与底层IO设备的交互次数,提高了数据传输的效率。同时,缓冲流还提供了更为灵活的数据读写方式,如按行读取、按指定字符序列读取等。
字符集转换层:字符类输入输出流InputStreamReader和OutputStreamWriter等类提供了字符集转换功能。由于字节流(InputStream和OutputStream)是按照字节来读取和写入数据的,而字符流(Reader和Writer)则是按照字符来处理的。因此,在字节流和字符流之间需要进行字符集转换。InputStreamReader和OutputStreamWriter类就是用于实现这种转换的。它们可以将字节流转换为字符流,或者将字符流转换为字节流,以便在不同的字符集编码之间进行数据交换。
特殊功能层:Java还提供了许多用于特定场景的I/O类。这些类在底层I/O操作的基础上,添加了额外的功能以满足特定的需求。例如,CipherInputStream和CipherOutputStream类用于加密解密数据的读写操作;ZipInputStream和ZipOutputStream类用于压缩解压缩数据的读写操作;DataInputStream和DataOutputStream类用于以特定的格式(如基本数据类型和字符串)读写数据;File类则提供了对文件和目录的访问和操作功能。
Java的I/O库通过层次化的设计,为开发者提供了灵活、强大且易于使用的I/O操作功能。
二、Java 输入与输出(I\O)系统之文件和目录(File类)
Java语言自从JDK 1.0开始的输入/输出(Input/Output,简称I/O)是同步阻塞式IO(简称BIO)。它使用java.io.File类对象来管理文件和目录。
- File类
Java.io中的大多数类可以完成内存和磁盘的数据交换功能,但File(文件)类例外,它是唯一一个与文件本身有关的操作类。
File类是java.io包下代表与平台无关的文件和目录,Java程序操作文件和目录,都可以通过File类来完成。但是访问文件内容本身,要通过输入/输出流。
关于File类的详细介绍请参见上篇博客:Java的File类详解
三、Java 输入与输出(I\O)系统之输入/输出流
JDK 1.0 最初的输入/输出(I/O)是面向输入/输出流的,输入/输出流是单向非缓冲的,它只支持字节流(InputStream、OutputStream)和字符流(Reader、Writer)两种。它采用阻塞模式,在读/写数据时,如果没有可以读取/写入的数据信息时会发生阻塞,简称BIO。
Javar 输入/输出流(InputStream/OutputStream),与Jdk 8.0中引入的流(Stream)是完全不同的概念。
Java的输入/输出(I/O)流是个抽象的概念,它是对输入输出设备读写数据的抽象。输入流可以看作一个输入通道,输出流可以看作一个输出通道。
输入流和输出流则是一个相对的概念,通常以程序为参考基点,Java输入流和输出流的主要区别在于数据的流向:当程序从某个数据源读入数据的时候,就会开启一个数据输入流(InputStream),数据源可以是文件、内存或网络等等。反之,写出数据到某个数据源目的地的时候,也会开启一个数据输出流(OutputStream),这个数据源目的地也可以是文件、内存或网络等等。
输入/输出流(InputStream/OutputStream)示意图:
Javar 输入/输出流(InputStream/OutputStream)的分类:
-
按照I/O流的方向:可分为输入流(只能从中读取数据,不能向其写入数据)和输出流(只能向其写入数据,不能从中读取数据)。输入流使用InpurtStream和Reader作为基类;输出流使用OutputStream和Writer作为基类。
-
按照I/O流处理的数据单元:可分为字节流(以字节为处理单元)和字符流(以字符为处理单元);
-
按照I/O流处理功能:可分为低级的节点流和高级的装饰流(又称处理流)。
节点流是直接从数据源读/写数据的输入输出流;
节点流的示意图:
装饰流(又称处理流),是指用于对一个已存在的输入输出流进行装饰,装饰流在不改变数据信息的情况下添加额外的增强功能。因为装饰流不会直接连接到实际的数据源,不能够直接获取数据,所以构建一个装饰流时,需要在构造函数中传入一个节点流。装饰流可进行多层嵌套装饰。
例如,BufferedInputStream是FileInputStream的装饰流,它为文件输入流提供缓冲功能,从而提高读取文件的效率。
(一)、字节输入与输出流
字节输入与输出流
在程序设计中,程序如果要读取或写入8位bit的字节数据,应该使用字节流来处理。字节流一般用于读取或写入二进制数据,如图片、音频文件等。字节流主要用于处理“非文本数据”的应用场景。
在计算机中,无论是文本、图片、音频还是视频,所有的文件都能以二进制比特(bit,1字节为8bit)形式传输或保存。Java语言对字节输入/输出操作提供了一系列流,统称为字节流。程序需要读取数据的时候使用输入流;而当程序需要输出或保存数据的时候使用输出流。在 Java 中,字节流提供了两个抽象基类 InputStream
和 OutputStream
,分别用于处理字节流的输入和输出。因为抽象类不能被实例化,所以实际使用的是这两个类的实现子类。
InputStream类是字节输入流的抽象基类,InputStream 类的常用子类如下所示:
字节输入流的层次结构图:
InputStream是字节输入流的抽象基类,InputStream的基本功能是从数据源读取数据,它定义了几个通用的方法:
- read( ):读取一个字节并将读取的字节作为int返回。当到达输入流的结尾时,它返回-1。
- read(byte[] b):从流中读取b的长度个字节的数据存储到b中,返回结果是读取的字节个数(当再次读时,如果返回-1说明到了输入流的结尾了,没有数据了)
- read(byte[] b, int off, int len):从流中从off的位置开始读取len个字节的数据存储到b中,返回结果是实际读取到的字节个数(当再次读时,如果返回-1说明到了输入流的结尾,没有数据了)
- close():关闭流,释放资源。
OutputStream类是字节输出流的抽象基类,OutputStream 类的常用子类如下所示:
字节输出流的层次结构图:
OutputStream是字节输出流的基类, OutputStream作为基类,给它的基类定义了几个通用的方法:
- write(byte[] b):将b的长度个字节数据写到输出流中。
- write(byte[] b,int off,int len):从b的off位置开始,获取len个字节数据,写到输出流中。
- flush():刷新输出流,把数据马上写到输出流中。
- close():关闭流,释放系统资源。
常用的字节输出流:
- FileOutputStream是用于写文件的输出流,它除了可以使用基类定义的方法外,还实现了OutputStream的抽象方法write(int b):
write(int b):将b转成一个字节数据,写到输出流中。 - BufferedOutputStream像上面那个BufferedInputStream一样,都可以提高效率。它除了可以使用基类定义的方法外,它还实现了OutputStream的抽象方法write(int b):
write(int b):将b转成一个字节数据,写到输出流中。
(二)、字符输入与输出流
字符流是为了高效处理字符而引入的。其实质是在字节流读取时,去字符表中查了指定的码点。
字节流和字符流的区别
-
处理的数据单元不同:
字节流:以字节(8bit)为单位,一次读入或写出是8位比特。
字符流:以字符为单位,根据码表映射字符,一次可能读取不同个数的字节。字符流一次读入或写出至少是一个码元。根据字符编码集不同读入的字节数是不同的。例如:UTF-8码元长度是8位比特,一个字符需要1-6个码元来表示;UTF-16码元长度是16位比特,一个字符最多需要2个码元来表示。 -
处理对象不同:字节流能处理所有类型的数据(如图片、音视频等),而字符流只能处理字符类型的数据。设备上的数据信息无论是图片、视频或者文字,它们都以二进制形式存储。二进制最终都是以字节(8bit)为数据单元进行处理的,所以计算机中的最小数据单元就是字节。因此,字节流可以处理设备上的所有数据信息。只是字节流处理字符数据会比字符流复杂一些。
应用场景: 处理纯文本数据时优先考虑使用字符流;否则一律使用字节流。
Reader类是字符输入流的抽象基类,Reader 类的常用子类如下所示:
Reader是字符输入流的抽象基类 ,它定义了以下几个方法:
- read() :读取单个字符,返回结果是一个int,需要转成char;到达流的末尾时,返回-1
- read(char[] cbuf):读取cbuf的长度个字符到cbuf这种,返回结果是读取的字符数,到达流的末尾时,返回-1
- close() :关闭流,释放占用的系统资源。
常用的字符输入流,如下所示:
- InputStreamReader 可以把InputStream中的字节数据流根据字符编码方式转成字符数据流。它除了可以使用基类定义的方法,它自己还实现了以下方法:
read(char[] cbuf, int offset, int length) :从offset位置开始,读取length个字符到cbuf中,返回结果是实际读取的字符数,到达流的末尾时,返回-1 - FileReader 可以把FileInputStream中的字节数据转成根据字符编码方式转成字符数据流。
- BufferedReader可以把字符输入流进行封装,将数据进行缓冲,提高读取效率。它除了可以使用基类定义的方法,它自己还实现了以下方法:
(1),read(char[] cbuf, int offset, int length) :从offset位置开始,读取length个字符到cbuf中,返回结果是实际读取的字符数,到达流的末尾时,返回-1
(2),readLine() :读取一个文本行,以行结束符作为末尾,返回结果是读取的字符串。如果已到达流末尾,则返回 null
Writer类是字符输出流的抽象基类,Writer 类的常用子类如下所示:
Writer是字符输出流的抽象基类, ,它定义了以下几个方法:
write(char[] cbuf) :往输出流写入一个字符数组。
write(int c) :往输出流写入一个字符。
write(String str) :往输出流写入一串字符串。
write(String str, int off, int len) :往输出流写入字符串的一部分。
close() :关闭流,释放资源。
flush():刷新输出流,把数据马上写到输出流中。
常用的字符输出流实现类:
OutputStreamWriter可以使我们直接往流中写字符串数据,它里面会帮我们根据字符编码方式来把字符数据转成字节数据再写给输出流,它相当于一个中介/桥梁。
FileWriter与OutputStreamWriter功能类似,我们可以直接往流中写字符串数据,FileWriter内部会根据字符编码方式来把字符数据转成字节数据再写给输出流。
BufferedWriter比FileWriter还高级一点,它利用了缓冲区来提高写的效率。它还增加了一个方法:
newLine() :写入一个换行符。
四、Java 输入与输出(I\O)系统之标准输入/输出/错误流
计算机系统都有标准的输入设备和标准输出设备。普通计算机,标准输入设备通常是键盘,而标准输出设备是屏幕。Java程序经常需要从键盘上输入数据,在屏幕上输出数据,因此,Java系统预定义了三个标准输入/输出流,两个对象System.in和System.out,分别代表来自键盘的标准输入和往屏幕的标准输出,另一个标准错误输出流System.err对象也向屏幕输出。
System.in是标准输入流,它是InputStream类的实例对象。可以使用read()方法从键盘上读取字节,也可以装饰成数据流读取各种类型的数据和字符串。
System.out和System.err是标准输出流和标准错误输出流,是OutputStream实现类PrintStream类的实例。
下面是一个使用System.in输入流获取用户键盘信息的例程:
import java.io.IOException;
public class SystemInTest {
public static void main(String[] args) throws IOException {
char ch;
System.out.print("请输入一个字符,并按回车:");
ch = (char)System.in.read();
System.out.print("你输入的字符是:" + ch);
}
} //SystemInTest例程结束。
说明: 此例程只支持录入一个(次)英文字母。
上面的代码使用了标准输入对象System.in的read()方法。需要注意的是,在键盘上按下任何一个键都会被当作输入值读入,包括按下回车键(Enter)键。当按下回车键(Enter)键时,实际上发送了两个键值,一个是回车’\r’,另一个是换行’\n’。例程因为只读了一次字符信息,后两次实际上被忽略了。如果使用如下代码就能捕捉到,会出现两次换行操作。
while (true) {
System.out.print("请输入一个字符,并按回车:");
ch = (char)System.in.read();
System.out.print("你输入的字符是:" + ch);
}
五、Java 输入/输出流的常用实现类
未完待续。
参考文献:
- Java输入输出流
- 【博客园】输入/输出-竹马今安在