高薪程序员&面试题精讲系列22之说说Java的IO流,常用哪些IO流?

一. 面试题及剖析

1. 今日面试题

今天 壹哥 带各位复习一块可能会令初学者比较头疼的内容,起码当时让我很有些头疼的内容,那就是I/O流。为啥I/O流会让很多初学者头疼呢?其实主要是因为I/O流的分类实在是太多了,一会是输入流,一会是输出流,还有字节流、字符流、文件输入流,文件输出流,缓冲流.....乱七八糟一大堆,光是这些英文单词把人背的脑袋都大了。

正因为如此,面试官就喜欢在这里考察我们的Java基础,常见的I/O流题目如下:

说一下Java中的I/O流有哪些?

你常用哪些I/O流?

输入流、输出流的区别?

......

2. 题目剖析

我们在开发时,用到I/O流的地方有很多,比如文件的上传下载,数据传输、存储,音视频编解码操作等,这些都是很重要的操作,所以面试官就很喜欢考察我们这一块的基础是否扎实。如果我们I/O流的基础不够扎实,就很难写出健壮高效的偏底层代码。

所以接下来 壹哥 会带各位详细全面的梳理一下I/O流的内容,以后再遇到有关I/O流的面试题,直接把本博客中的内容甩到面试官脸上即可,HIA HIA。

二. I/O流

1. 概念

首先 壹哥 来解释一下 “I/O流” 这个概念,如果我们连 I/O流 是个啥都不知道,也就没必要继续往下看了。在这里 壹哥 会从两部分展开介绍I/O流,即 “I/O” 与 “流”

1.1 I/O

I/O中间用 “/”斜杠 分割,其实代表的是两个内容,即 “I” 与 “O”,分别是 “In” 与 “Out” 的缩写。

In:输入,代表着能够接收数据的数据源对象;

Out:输出,代表着能够产出数据的数据源对象。

1.2 流

接下来咱们再来看看什么是 “流”!请先在脑海里想一下,“流”是一种什么样的形态?其实Java中各个API的命名都是很形象的,绝对都做到了见名知意。

这里 “流” 就是一个很形象的概念!当我们的代码程序需要 读入数据 的时候,可以开启一个连通 数据源 流(输入流),这个数据源可以是文件、内存、数据库,或是网络连接。同样的,当我们的代码程序需要 输出数据 的时候,可以开启一个连通 目的地 流(输出流)这个目的地一般是指我们的代码程序。这时候你可以想象一下,我们的数据好像就在数据源与目的地之间 “流动” 起来了一样。

其实 流(stream)这个概念,一开始源于UNIX中的管道(pipe)概念在UNIX中,管道是一条不间断的字节流,用来实现程序或进程间的通信,或读写外围设备、外部文件等。

最后 壹哥 再给各位提取一下流的概念:

流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

2. 作用

而且从上面我对I/O流的描述中,我们也可以抽取出I/O流的核心作用,如下:

I/O流可以在数据源和目的地之间搭建一个传输通道,用于处理设备与代码程序之间的数据传输,设备是指硬盘、内存、数据库、键盘录入、网络等。

一言以蔽之,I/O流屏蔽了实际的I/O设备中处理数据的各种细节,我们不必关心其内部具体的流动过程,只需知道I/O流可以用来处理设备之间的数据传输即可。

3. 分类

如果只看上面关于I/O流的概念,感觉也并没有什么难度,但是对I/O流的学习,最难的是在于其分类实在是太多。I/O流中有着不同的划分维度,如果我们根据这些不同的标准来分类的话,可以分类如下:

  1. 按I/O流的流动方向分为:输入流和输出流;
  2. 按I/O流的数据单位不同分为:字节流和字符流;
  3. 按I/O流的功能不同分为:节点流和处理流。

接下来 壹哥 再分别对这3种分类进行详解一下。

3.1 输入流与输出流

从I/O流的流动方向上,我们知道I/O流其实可以分为输入流与输出流,但是不少初学者总是分不清输入流与输出流,甚至会把两者搞反。所以接下来 壹哥 就再明确一下输入流与输出流的区别,我们来看下图:

在上图中,我们以家中自来水的进水与出水来形象的比喻输入流与输出流。

自来水公司相当于是数据源,我们家中的房子就相当于是目的地。自来水公司的水进入到我们家里,这就是自来水的输入;我们家中产生的污水,要排到污水处理厂,这就是自来水的输出。

在这个自来水供水、排水的过程中,我们可以想一下,输入、输出是不是一个相对的概念呢?那么相对于哪个角色呢?没错!输入、输出都是相对于我们的房子来说的,进入到房子叫做输入,流出房子叫做输出。

壹哥 再把上图中的各角色明确一下:

数据源文件:就是上图中自来水公司的水池,用于提供自来水(数据);

输入流:从自来水公司进到房子里的管道流,携带着具体的数据到家里来;

目的地:就是上图中的房子,也就是我们项目的代码程序,或者说是内存;

输出流:从家中流出到污水厂的管道流,携带着具体的数据到污水厂;

数据目标文件:最后的污水厂,其实也就是用于持久化存储污水(数据)的地方,其实也是一种数据源。

所以,I/O流中的输入流与输出流,入与出都是相对于内存而言的。从某个数据源读取数据到内存中,被称为输入流;从内存中把数据持久化保存到其他设备上,则被称为输出流。简单一句话,流向内存是输入流,流出内存的输出流。我们再来看下图:

另外要注意,我们可以从输入流中读取信息,但不能对它写;可以对输出流进行写操作,但不能从中读。所以输入也叫做读取数据,输出也叫做作写出数据

至此,你应该不会再把输入流和输出流搞反了吧,是不是应该给 壹哥 点个赞,hiahia......

3.2 字节流与字符流

上面 壹哥 说了,按照数据流的数据单位不同,I/O流可以分为字节流与字符流,两者的区别如下:

字节流:字节流以字节(8bit)为单位,一次读入或写出8位二进制数据字节流能处理所有类型的数据(如图片、音频、视频等)

字符流:字符流以字符为单位,根据码表映射字符,一次可能读多个字节,一次读入或写出16位二进制数据;字符流只能处理字符类型的数据

这是因为在Java中,一个字节的空间是1个Byte,即8位;而一个字符的空间是2个Byte,即16位。另外Java的字节是有符号类型,字符是无符号类型!

另外我们要知道,在计算机里,一切文件数据(文本、图片、视频等)在存储时,都是以二进制的形式保存的,即都是一个一个的字节,在传输时也一样如此。所以,字节流可以传输任意类型的文件数据在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。

但是因为数据编码的不同,为了对字符进行高效的操作,就有了字符流。字符流的本质其实也是基于字节流,在进行读取时去查了指定的码表,而字节流直接读取数据会有乱码的问题(读中文会乱码)。

所以字节流和字符流的原理其实也是相同的,只不过处理的数据单位大小不同而已。一般在Java关于I/O流的API里,后缀是Stream的是字节流,而后缀是Reader和Writer的是字符流。

你可能会问,我们开发时,到底是该选择字节流还是字符流呢?

对于字节流和字符流,如果我们只是要处理纯文本数据,可以优先考虑字符流。 除此之外尽量使用字节流。

3.3 节点流与处理流

另外如果从I/O流的功能角度来看,I/O流可以分为节点流和处理流,两者区别如下:

节点流:直接与数据源相连,读入或写出。

但是如果我们直接使用节点流进行操作,读写并不方便,所以为了更快的读写文件,才有了处理流。

处理流:一般会与节点流一起使用,在节点流的基础上,再套接一层,套接在节点流上的就是处理流。

4. I/O流相关API

在Java中,关于I/O流的API,共有四大类,分别如下:

InputStream---字节输入流;

OutputStream---字节输出流;

Reader-----字符输入流;

Writer------字符输出流。

即Java中字节流的抽象基类有如下2个

InputStream,OutputStream

InputStream与OutputStream类关系如下图所示:

字符流的抽象基类有如下2个:

Reader,Writer

Reader与Writer类关系如下图所示:

我们可以用下图概括展示:

由这四个基类派生出来的子类名称,都是以其父类名作为子类名的后缀,如InputStream的子类FileInputStream,Reader的子类FileReader。

4.1 InputStream字节输入流

InputStream的常用方法:

int available() 从下一次调用此输入流的方法返回可从该输入流读取(或跳过)的字节数,而不会阻塞。

void close() 关闭此输入流并释放与流相关联的任何系统资源。 

void mark(int readlimit) 标记此输入流中的当前位置。 

boolean markSupported() 测试此输入流是否支持 mark和 reset方法。 

abstract int read() 从输入流读取数据的下一个字节。 

int read(byte[] b) 从输入流中读取一些字节数,并将它们存储到缓冲器阵列 b。 

int read(byte[] b, int off, int len) 从输入流读取最多 len个字节的数据到字节数组。

byte[] readAllBytes() 从输入流读取所有剩余字节。 

int readNBytes(byte[] b, int off, int len) 将所请求的字节数从输入流读入给定的字节数组。 

void reset() 将此流重新定位到最后在此输入流上调用 mark方法时的位置。 

long skip(long n) 跳过并丢弃来自此输入流的 n字节的数据。 

long transferTo(OutputStream out) 从该输入流中读取所有字节,并按读取的顺序将字节写入给定的输出流。

4.2 OutputStream字节输出流

OutputStream字节输出流的方法:

void close() 关闭此输出流并释放与此流相关联的任何系统资源。 

void flush() 刷新此输出流并强制任何缓冲的输出字节被写出。 

void write(byte[] b) 将 b.length字节从指定的字节数组写入此输出流。 

void write(byte[] b, int off, int len) 从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。 

abstract void write(int b) 将指定的字节写入此输出流。 

4.3 Reader字符输入流

Reader主要方法如下:

abstract void close() 关闭流并释放与之相关联的任何系统资源。 

void mark(int readAheadLimit) 标记流中的当前位置。 

boolean markSupported() 告诉这个流是否支持mark()操作。 

int read() 读一个字符 

int read(char[] cbuf) 将字符读入数组。

abstract int read(char[] cbuf, int off, int len) 将字符读入数组的一部分。 

int read(CharBuffer target) 尝试将字符读入指定的字符缓冲区。

boolean ready() 告诉这个流是否准备好被读取。 

void reset() 重置流。 

long skip(long n) 跳过字符 

4.4 Writer字符输出流

Writer的主要方法如下:

Writer append(char c) 将指定的字符附加到此writer

Writer append(CharSequence csq) 将指定的字符序列附加到此writer

Writer append(CharSequence csq, int start, int end) 将指定字符序列的子序列附加到此writer

abstract void close() 关闭流,先刷新

abstract void flush() 刷新流 

void write(char[] cbuf) 写入一个字符数组。 

abstract void write(char[] cbuf, int off, int len) 写入字符数组的一部分 

void write(int c) 写一个字符 

void write(String str) 写一个字符串 

void write(String str, int off, int len) 写一个字符串的一部分

4.5 完整API图

以上这些java.io包中的API,我给大家绘制了下图:

从上图中,我们可以看到,有各种各样的I/O流相关的API类,我们可以简单归纳并做如下简介:

  • 文件操作流

    • FileInputStream(字节输入流);
    • FileOutputStream(字节输出流);
    • FileReader(字符输入流);
    • FileWriter(字符输出流)
  • 管道操作流

    • PipedInputStream(字节输入流);
    • PipedOutStream(字节输出流);
    • PipedReader(字符输入流);
    • PipedWriter(字符输出流)

注意:

PipedInputStream的一个实例要和PipedOutputStream的一个实例共同使用,共同完成管道的读取写入操作,主要用于线程操作。

  • 字节/字符数组操作流

    • ByteArrayInputStream(字节数组输入流);
    • ByteArrayOutputStream(字节数组输出流);
    • CharArrayReader(字符数组输入流);
    • CharArrayWriter(字符数组输出流)
  • Buffered缓冲流

    • BufferedInputStream(字节缓冲区输入流);
    • BufferedOutputStream(字节缓冲区输出流);
    • BufferedReader(字符缓冲区输入流);
    • BufferedWriter(字符缓冲区输出流)

注意:

这是带缓冲区的处理流,缓冲流的底层从具体设备上获取数据,并将数据存储到缓冲区的数组内,通过缓冲区的read()方法从缓冲区获取具体的字符数据,这样就避免了每次都和硬盘打交道,提高了数据访问效率。

  • 转化流

    • InputStreamReader
    • OutputStreamWriter

注意:

转换流,从字面意思可以看出它是字节流与字符流之间的桥梁,可以将字节转为字符,或者将字符转为字节。

  • 数据流

    • DataInputStream
    • DataOutputStream

注意:

数据流可以解决我们输出数据类型的困难,数据流可以直接输出float类型或long类型,提高了数据读写的效率。

  • 打印流

    • PrintStream
    • PrintWriter

注意:

一般是打印到控制台,可以进行控制打印的地方。

  • 对象流

    • ObjectInputStream
    • ObjectOutputStream

注意:

ObjectOutputStream可以将Java对象的原始数据类型写出到文件,实现对象的持久存储;

ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。

  • 序列化流

    • SequenceInputStream

三. 参考答案

1. Java中的I/O流有哪些

回到我们的面试题上面来,Java中的I/O流有哪些,其实用下图即可回答。

2. 常用I/O有哪些

我们知道,Java中的I/O流有很多,但是开发时并不是每一个都经常用到的,所以 壹哥 用下图给各位展示了常用的I/O流,下图中凡是带有中文解释的I/O流,基本就是我们开发时最常用的。

四. 结论

最后我们把IO流再简单总结一下。

1. 哪些流可以提高读写性能?

针对读写对象的不同,

字节流可以采用带缓冲区的BufferedInputStream和BufferedOutputStream;

字符流可以采用带缓冲区的BufferedReader和BufferedWriter。

2. Java有几种类型的流?

  1. 按I/O流的流动方向分为:输入流和输出流;
  2. 按I/O流的数据单位不同分为:字节流和字符流;
  3. 按I/O流的功能不同分为:节点流和处理流。

3. Java中I/O流相关API

字节流:

InputStream;

OutputStream

字符流:

Reader;

Writer

Java中其他各种流都是由它们派生出来的。

4. 操作文本文件用什么I/O流?

FileReader

FileWriter

5. 操作基本数据类型和String类型用什么流?

DataInputStream

DataOutputStream

6. 哪些I/O流可以指定字符编码?

BufferedReader

BufferedWriter

BufferedInputStream

BufferedOutputStream

7. 各I/O流API作用

FileInputStream/FileOutputStream:需要逐个字节处理原始二进制流的时候使用,效率低下。

FileReader/FileWriter:需要组个字符处理的时候使用。

StringReader/StringWriter:需要处理字符串的时候,可以将字符串保存为字符数组。

PrintStream/PrintWriter:用来包装FileOutputStream 对象,方便直接将String字符串写入文件。

Scanner:用来包装System.in流,很方便地将输入的String字符串转换成需要的数据类型。

InputStreamReader/OutputStreamReader,:字节和字符的转换桥梁,在网络通信或者处理键盘输入的时候用。

BufferedReader/BufferedWriter, BufferedInputStream/BufferedOutputStream:缓冲流用来包装字节流后者字符流,提升IO性能,BufferedReader还可以方便地读取一行,简化编程。

SequenceInputStream(InputStream s1, InputStream s2):序列流,合并流对象时使用。

ObjectInputStream、ObjectOutputStream:方法用于序列化对象并将它们写入一个流,另一个方法用于读取流并反序列化对象。

ByteArrayInputStream、ByteArrayOutputStream:用于操作字节数组。

DataInputStream、DataOutputStream:操作基本数据类型和字符串。

8. 开发时到底该选择哪种流?

我们现在已经知道了这么多的 I/O流 分类,那在开发时该选择使用哪种呢?什么时候用输出流?什么时候用字节流?我们可以根据下面三步选择适合自己的流:

  1. 首先到底该选择输入流还是输出流,这就要根据自己实际需求的情况来决定,如果想从程序内存中输出数据到别的设备中,那么就选择输出流,反之就选输入流;
  2. 然后我们再考虑数据输出时,每次是要传递一个字节还是两个字节,每次传输一个字节就选字节流,如果存在中文,那肯定就要选字符流了;
  3. 通过前面两步,我们就可以选出一个合适的节点流了,比如字节输入流 InputStream,如果要在此基础上增强功能,那么就在处理流中再选择一个合适的即可。

至此,壹哥 就把I/O流中的核心内容带大家都复习了一下,当然还有挺多细节没有总结到位,毕竟我这里是面试题梳理总结,不是专门的I/O流教程,请各位再结合之前学习I/O流时的内容,形成自己的知识脉络。如果你有什么想法,可以在评论区留言讨论哦。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码

)">
< <上一篇
下一篇>>