资讯专栏INFORMATION COLUMN

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

fnngj / 3112人阅读

摘要:一面试题及剖析今日面试题今天壹哥带各位复习一块可能会令初学者比较头疼的内容,起码当时让我很有些头疼的内容,那就是流。在这里壹哥会从两部分展开介绍流,即与流。除此之外尽量使用字节流。关闭此输入流并释放与流相关联的任何系统资源。

一. 面试题及剖析

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) 将指定的字符附加到此writerWriter append(CharSequence csq) 将指定的字符序列附加到此writerWriter append(CharSequence csq, int start, int end) 将指定字符序列的子序列附加到此writerabstract 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流时的内容,形成自己的知识脉络。如果你有什么想法,可以在评论区留言讨论哦。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/124493.html

相关文章

  • 阿里路+Java面经考点

    摘要:我的是忙碌的一年,从年初备战实习春招,年三十都在死磕源码,三月份经历了阿里五次面试,四月顺利收到实习。因为我心理很清楚,我的目标是阿里。所以在收到阿里之后的那晚,我重新规划了接下来的学习计划,将我的短期目标更新成拿下阿里转正。 我的2017是忙碌的一年,从年初备战实习春招,年三十都在死磕JDK源码,三月份经历了阿里五次面试,四月顺利收到实习offer。然后五月怀着忐忑的心情开始了蚂蚁金...

    姘搁『 评论0 收藏0
  • Java编程基础23——IO(其他)&Properties

    摘要:但它融合了和的功能。支持对随机访问文件的读取和写入。的概述和作为集合的使用了解的概述类表示了一个持久的属性集。可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。 1_序列流(了解) 1.什么是序列流 序列流可以把多个字节输入流整合成一个, 从序列流中读取数据时, 将从被整合的第一个流开始读, 读完一个之后继续读第二个, 以此类推. 2.使用方式 整合两个: S...

    vvpale 评论0 收藏0
  • Java编程基础22——IO(字符)&字符其他内容&递归

    摘要:字符流字符流是什么字符流是可以直接读写字符的流字符流读取字符就要先读取到字节数据然后转为字符如果要写出字符需要把字符转为字节再写出类的方法可以按照字符大小读取通过项目默认的码表一次读取一个字符赋值给将读到的字符强转后打印字符流类的方法可以 1_字符流FileReader 1.字符流是什么 字符流是可以直接读写字符的IO流 字符流读取字符, 就要先读取到字节数据, 然后转为字符. ...

    BoYang 评论0 收藏0
  • Java汇总,持续更新到GitHub

    摘要:目录介绍问题汇总具体问题好消息博客笔记大汇总年月到至今,包括基础及深入知识点,技术博客,学习笔记等等,还包括平时开发中遇到的汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善开源的文件是格式的同时也开源了生活博客,从年 目录介绍 00.Java问题汇总 01.具体问题 好消息 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技...

    beita 评论0 收藏0
  • 转换05_InputStreamReader介绍&代码实

    摘要:是字节流通向字符流的桥梁它使用指定的读取字节并将其解码为字符。解码把看不懂的变成能看懂的继承自父类的共性成员方法读取单个字符并返回。一次读取多个字符将字符读入数组。关闭该流并释放与之关联的所有资源。构造方法创建一个使用默认字符集的。 package com.itheima.demo03.ReverseStream; import java.io.FileInputStream;impo...

    alanoddsoff 评论0 收藏0

发表评论

0条评论

fnngj

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<