Java IO 学习笔记(一)


Java IO 学习笔记(一)

一、什么是流

任何程序,都应该有输入输出的方式。我们难以想象,无法进行输入和输出的程序,能有什么作用。因为这时候的它,与外界是无法联系的。

比方说,我们的 web 服务器。它需要从用户那边读取用户的请求信息,然后再根据请求信息选择向用户响应些什么。如果 web 程序没有输入输出的方式,那上述的请求与响应自然也就无法实现了。

因此,任何编程语言,都需要为开发人员提供输入和输出的方式,而这种输入和输出,就是基于流(Stream)实现的。

流,看起来像是一个挺抽象的概念,但如果你把现实中的溪流与它作比较的话,又会觉得它很形象。它其实就是一条连接程序和外界的“溪流”。当程序需要读取数据的时候,就会开启一个通向数据源的流,这个数据源可以是文件、内存或是网络连接;类似的,当程序需要写入数据的时候,就会开启一个通向目的地的流。这时候,数据就好比溪流中的水,在流中“流动”。

基于以上,我们可以这么说,流,是程序输入与输出的方式。

二、Java IO

Java IO 实际上就是 Java 语言以流为基础实现数据输入与输出功能的类库。 在 Java IO 中,所有的数据都会被串行化写入输出流或者是从输入流读入。

要全面地学习 Java IO 不是一件容易的事,因为这是一个内容十分庞大的类库,涉及到非常多领域的数据传输。

这里需要指出一点,很多新手在理解 Java IO 的输出与输入时,会将其局限在程序与文件系统的交互中。然而,文件的读写,只能算是 Java IO 中的一部分,实际上,Java IO 还涉及到网络通信、对象传输等等一系列领域。

三、流的分类

  • 按流向分:
    • 输入流:程序可以从中读取数据的流。
    • 输出流:程序能向其中写入数据的流。
  • 按传输单位分:
    • 字节流:以字节为单位传输数据的流。
    • 字符流:以字符为单位传输数据的流。

输入流和输出流是比较好理解的,这里不再赘述。但是对于新人来说,可能容易把字节流和字符流混淆,甚至是压根不知道这两者具体指的是什么,因此,这里我们需要再介绍一些基础概念。

  • 比特(Bit): binary digit(二进制数位)的缩写,又称“位”,是计算机信息量的最小单位,同时也是二进制数字中的位。
  • 字节(Binary): 计算机中信息计量的一种单位,1 个字节由 8 个位组成,例如:00000001。
  • 字符(Character): 文字与符号的总称,可以是各个国家的文字、标点符号、图形符号、数字等。

有了这些基础概念做铺垫后,我们再来谈谈字节流和字符流。

从字面上我们就可以理解,字节流就是以字节为单位处理数据的流。它只能读取或者输出字节,一组由 8 个位(Bit)组成的数据(如 00000001 等),也就是计算机所擅长识别的数据。如果你想把我们人类所能理解的字符交给它传输,很抱歉,它做不到。

那么,一个无法识别人类语言的程序,有什么作用呢?因此,就有了字符流的诞生。

顾名思义,字符流就是用来传输字符的流。它可以将计算机中的二进制数据进行处理后,转化成我们人类所能识别的字符。但正是字符流的这种作用,才会给我们初学 IO 时带来种种困难。因为它涉及到了计算机语言和人类语言的相互转化,也就是我们常提到的编码与解码。事实表明,实际开发中频繁出现的乱码问题,其根源就在于编码与解码的不一致。

讲到这里,如果还是不能理解字节流和字符流的差别的话,也不用着急,因为日后开发中踩到的乱码坑多了,自然也就能理解了。(一脸坏笑)

当然,如果对编码与解码有兴趣的朋友,不妨阅读此文:字符编码的前世今生

四、基本接口

Java IO 有两组基本接口,一组基于字节,另一组基于字符。在本文中,我们先介绍基于字节的操作接口,分别是 InputStream 和 OutputStream。至于字符流的操作接口,我们将在之后的学习笔记中进行总结。

4.1 InputStream

InputStream 是基于字节的输入流。它本身是一个抽象类,具体的实现由子类负责。

如图所示,在 InputStream 中,包含以下方法:

image

这些方法的具体作用,请见下面的表格:

方法名 作用
read(byte[] b) 从流中读取一组字节,并将其保存在一个字节数组中。若该字节数组的长度为 0,则函数不读取任何字节,并返回 0;若流中已经没有字节可以读取,则函数返回 -1。
read(byte[] b, int off, int len) 从流中的某个位置读取指定长度的字节,并保存到字节数组的相同位置中。其中,第一个 int 类型参数代表开始读取的位置,第二个 int 参数代表准备读取的长度。
skip(long n) 允许程序跳过流中的 n 个字节。
available() 返回流中剩余的可读字节数。
close() 关闭流,并释放与该流有关联的系统资源。
mark(int readlimit) 允许程序在读取流的过程中,记录指定的位置,通常配合 reset 方法使用。
reset() 允许程序返回到上次在流中所标记的位置,通常配合 mark 方法使用。
markSupported() 判断当前流是否支持 mark 和 reset。

这里我们少描述了一个方法,那就是 read()。这是为什么呢?首先,先让我们看看这个函数的完整说明。

/**
 * Reads the next byte of data from the input stream. The value byte is
 * returned as an <code>int</code> in the range <code>0</code> to
 * <code>255</code>. If no byte is available because the end of the stream
 * has been reached, the value <code>-1</code> is returned. This method
 * blocks until input data is available, the end of the stream is detected,
 * or an exception is thrown.
 *
 * <p> A subclass must provide an implementation of this method.
 *
 * @return     the next byte of data, or <code>-1</code> if the end of the
 *             stream is reached.
 * @exception  IOException  if an I/O error occurs.
 */
public abstract int read() throws IOException;

这段说明的大致意思是,read() 方法的作用是从流中读取下一个字节的值,并返回该字节的十进制结果(我们知道,一个字节是由 8 个 bit 组成,因此其范围是 00000000 ~ 11111111,换算成十进制就是从 0 到 255)。

但是我们也注意到,这个方法是一个抽象方法,它需要继承它的子类来完成具体的实现。如果这个方法没有被实现,那么我们上述表格中阐述的所有方法将没有任何意义。这也是我将这个方法拿出来单独介绍的原因。

4.2 OutputStream

OutputStream 是基于字节的输出流。它同样也是一个抽象类。

如图所示,在 OutputStream 中,包含以下方法:

image

很简单有木有!比 InputStream 少多了!

同样,这些方法的作用,可见下面表格:

方法名 作用
write(int b) 将某个字节,写到输出流中。需要注意的是,该方法同 read() 方法一样,也是抽象方法。
write(byte[] b) 将一个字节数组,写到输出流中。
write(byte[] b, int off, int len) 从字节数组的某个位置开始,输出指定长度的字节数到输出流中。
flush() 清理输出流中的缓存,同时强制输出流中的所有缓存字节。
close() 关闭流,并释放与该流有关联的系统资源。


五、类层次结构

InputStream 和 OutputStream 只是 Java IO 中的基础接口,两个接口下还有着丰富的实现类。下面,将分别介绍两个基本接口的类层次结构。(注:InputStream 和 OutputStream 的类层次结构在不同版本的 JDK 中会有所不同,本文是以 JDK 6 为基础的)

5.1 InputStream

image

在 InputStream 的体系中,会根据实现方式和处理的数据类型划分为不同的子类。尽管类别很多,但对于日常的开发而言,只要重点掌握 FileInputStream 和 FilterInputStream 即可。

  • FileInputStream: 顾名思义,FileInputStream 是用于从文件系统中读取字节的输入流。这里需要强调的是,它只能以字节的方式读取文件中的数据,而无法以字符的方式。无论用户在显示器上看到的是何种字符,通过 FileInputStream 读取到的也只是二进制数据的十进制表示。例如,文本中有一个字符 ‘a’,此时通过 FileInputStream 读取到的值是 97,转换成二进制数据则为 01100001,即 ‘a’ 这个字符在 ASCII 码表中对应的二进制数值。

  • FilterInputStream: 中文名为过滤流,是为了某种特定目的而过滤字节的数据流。例如,BufferedInputStream 在 InputStream 的基础上提供了缓冲区;再比如,DataInputStream 为整数、浮点数、字符串等的读取提供了可能。

5.2 OutputStream

image

输出流的类层次结构与输入流大体上是一致的。在 OutputStream 中,需要重点掌握的同样是 FileOutputStream 和 FilterOutputStream 两个子类。其具体作用不再说明,可参考输入流中的介绍。

写在最后:本篇只是个人在 Java IO 学习过程中的第一篇笔记,主要是从整体介绍一下 Java IO 的一些主要概念,在下一篇文章中,将针对 FileInputStream 和 FileOutputStream 展开详细的讨论。

猜你喜欢

转载自blog.csdn.net/magicpenta/article/details/78149231
今日推荐