如何应对Java的IO面试

可能很多朋友都和我一样,觉得IO不就输入输出流,读写操作吗?有什么好讲好写的?会用就行啦。但是现实却往往会给我们一个响亮的大耳光,往往在开发中我们会突然发现:妈的,IO是个什么鬼,我要怎么用?他能干什么?还有就是Java里面那么多IO类我要用哪一个?或者在参加面试的时候会被面试官劈头盖脸一顿骂:Java IO都没搞清楚你还敢说自己是做Android开发的?还敢说自己精通Android开发?还敢要这么高的薪资?(没错,这就是本人的亲身经历,但是怪谁呢?自己选的坑再深也要跳啊)所以为了避免这类情况的发生怎么办呢?学习是万能钥匙。

一、流概念和作用

流的概念:官方硬核解释又来了:代表任何有能力产出数据的数据源对象或者是有能力接受数据的接收端对象<Thinking in Java>

流的本质和作用:数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。 为数据源和目的地建立一个输送通道。

通常我们说IO都要和数据流结合在一起,因为没有数据流,IO操作也就没有意义了。Java中将输入输出抽象称为流,就好像水管,将两个容器连接起来。流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流.

流序列中的数据既可以是未经加工的原始二进制数据,也可以是经一定编码处理后符合某种格式规定的特定数据。因此Java中的流分为两种:

1) 、字节流:数据流中最小的数据单元是字节

2) 、字符流:数据流中最小的数据单元是字符, 字节流是以一个字节单位来运输的,比如一杯一杯的取水。而字符流是以多个字节来运输的,比如一桶一桶的取水,一桶水又可以分为几杯水。

但是真实的Java IO并不仅仅指这两种数据流。它主要分为以下三个层次:

(1)流式层-数据流:OutputStream、InputStream、Writer、Reader等

(2)非流式层-文件类:RandomAccessFile类和FileDescriptor等

(3)其他-文件读取部分的与安全相关的类:SerializablePermission类,以及与本地操作系统相关的文件系统的类,如:FileSystem类和Win32FileSystem类和WinNTFileSystem类。

 

不可否认,Java中的IO类太多了。但是这其中最重要的就是如下表中的5个类和一个接口。

类/接口

说明

File

文件类

InputStream

字节输入流

OutputStream

字节输出流

Reader

字符输入流

Writer

字符输出流

Serializable

序列化接口(可以看我之前关于序列化的文章,传送门

实际上这五个类一个接口就已经是Java IO的精髓了,但是要掌握它却并没有想象的那么容易。

在Android开发中,我们通常更多关注的是字节流(Stream)字符流(Reader/Writer)和 File/RandomAccessFile以及接口Serializable。

二、修饰模式在IO中的重要应用

在具体的学习流之前,我们必须要学的一个设计模式:装饰模式。因为从流的整个结构和类之间的依赖关系来看,都是沿用了修饰模式,都是一个类的功能可以用来修饰其他类,然后组合成为一个比较复杂的流。比如说:

DataOutputStream out = new DataOutputStream(
			   new BufferedOutputStream(
				new FileOutputStream(new File(filePath))));

从上面的代码块中大家不难看出这些类的关系:为了向文件中写入数据,首先需要创建一个实实在在的File文件,然后将这个文件修饰成FileOutputStream流来提供文件访问,但是FileOutputStream这个流是直接操作的磁盘,只能一个一个字节的去访问,所以它的访问效率很低。然后为了提升访问的效率,所以将它发送给具备缓存功能的BufferedOutputStream,BufferedOutputStream会开辟出一块内存区,然后每次向这个内存区写入,当内存区满时(或者手动flush时)一次性写入磁盘。可是内存区里面存放的是byte字节,如果我想写入Java基本类型的数据怎么办,为了实现与机器类型无关的java基本类型数据的输出,所以我们将缓存的流传递给了DataOutputStream这个能够输出Java基本数据类型的流。上面的关系其根本目的都是为outputSteam添加额外的功能。而这种额外功能的添加就是采用了装饰模式来构建的代码。因此,学习流,必须要学好装饰模式。

那么什么是装饰模式呢?相信很多像我一样没怎么学习过设计模式的朋友又有点懵逼了。所以这里就先了解一下什么是装饰模式

装饰模式

装饰模式也叫包装模式,它是结构型设计模式之一。官方一点的解释就是:它使用一种对客户透明的方式来动态地扩展对象的功能,同时它也是继承关系的一种替代方案之一。看着这种解释,是不是更加懵逼了?我们先来看看装饰模式的UML图或许更好理解一些。

  • OutputStream:统一的抽象组件,可以是一个接口或者抽象类,作为被装饰类的基本类型。
  • FileOutputStream:抽象组件的具体实现类,即被装饰的具体对象,他本身是个具有一些功能的完整的类。
  • FilterOutputStream:抽象装饰类,实现了OutputStream接口的同时还在内部维护了一个OutputStream的具体实现对象(即被装饰者,如:FileOutputStream),并可以通过构造函数初始化。而FilterOutputStream本身,通常采用默认实现,他的存在仅仅是一个声明:我要生产出一些用于装饰的子类了。而其子类才是赋有具体装饰效果的装饰产品类。
  • DataOutputStream和BufferedOutputStream:具体的装饰产品类,每一种装饰产品都具有特定的装饰效果。可以通过构造器声明装饰哪种类型的OutputStream,从而对其进行装饰。

一个设计模式的出现一定有他特殊的价值。仅仅看见上面的结构图你可能又会想,为何要兜这么一圈来实现?仅仅是想要多两个扩展方法,我直接继承FileOutputStream,或者直接在另一个OutputStream的实现类中实现不是一样吗?

  首先,装饰器的价值在于装饰,他并不影响被装饰类本身的核心功能,也就是不去改动现有代码的前提下去扩展新的需求的价值。这也是他被广泛使用的原因。在一个继承的体系中,子类通常是互斥的。比如一辆车,品牌只能要么是奥迪、要么是宝马,不可能同时属于奥迪和宝马,而品牌也是一辆车本身的重要属性特征。但当你想要给汽车喷漆,换坐垫,或者更换音响时,这些功能是互相可能兼容的,并且他们的存在不会影响车的核心属性:那就是他是一辆什么车。这时你就可以定义一个装饰器:喷了漆的车。不管他装饰的车是宝马还是奥迪,他的喷漆效果都可以实现。

三、字节流与字符流

学习IO基础,字节流和字符流当然不能不知道。那么如何学习这两种类型的数据流呢?他们之间又有什么区别呢?

字节流和字符流的区别:字节流读取单个字节,字符流读取单个字符(一个字符根据编码的不同,对应的字节也不同,如 UTF-8 编码是 3 个字节,中文编码是 2 个字节。)字节流用来处理二进制文件(图片、MP3、视频文件),字符流用来处理文本文件(可以看做是特殊的二进制文件,使用了某种编码,人可以阅读)。简而言之,字节是个计算机看的,字符才是给人看的。

(1)字节流

学习字节流我们需要根据上图的UML流程来学。为什么要按照一个学习路线来呢?原因是他们的功能和依赖关系决定的。

  OutputStream -> FileOutputStream/FilterOutputStream ->DataOutputStream->bufferedOutputStream

 从学习的角度来,我们应该先掌握FilterOutputStream, 以及FileOutputStream,这两个类是基本的类,从继承关系可以不难发现他们都是对 abstract 类 OutputStream的拓展,是它的子类。然而,伴随着对Stream流的功能的拓展,所以就出现了 DataOutputStream,(将java中的基础数据类型写入数据字节输出流中、保存在存储介质中、然后可以用DataOutputStream从存储介质中读取到程序中还原成java基础类型)。同时为了提升Stream的执行效率,所以出现了bufferedOutputStream。bufferedOutputStream就是将本地添加了一个缓存的数组。在使用bufferedOutputStream之前每次从磁盘读入数据的时候都是需要访问多少byte数据就向磁盘中读多少个byte的数据,而出现bufferedOutputSteam之后,策略就改了,会先读取整个缓存空间相应大小的数据,这样就是从磁盘读取了一块比较大的数据,然后缓存起来,从而减少了对磁盘的访问的次数以达到提升性能的目的。

另外一方面,我们知道了outputStream(输出流)的设计结构后,我们便可以知道如何使用outpuSteam了,同样的方法,我们可以运用到inputStream中来,inputStream相关类与OutputStream之间存在一定的对称关系(如下图),我们可以根据这种对称关系来关联学习,于是,我们对整个字节流就有了较清晰的理解,所以这样子我们就不会感觉到流的复杂了。这个时候对于其他的一些字节流的使用(byteArrayOutputStream/PipeOutputStream/ObjectOutputStream)的学习就只需要在使用的时候看看API即可。

 (2)字符流

字符流的学习我们依然能够套用字节流的学习套路。我们可以先来看看字符流类之间的关系图,会发现与字节流几乎一模一样。都是采用修饰模式来实现的整体框架。

 

很多人可能看了上面的图有点懵,为什么这里还有字节流OutputStream的身影呢?原因是字节流是直接操作文件本身的流,每次都是读写指定字节数。我们获取到的也是byte[]这样的原始字节,而字符流每次读取后返回给我们的都是字符串,这是因为每次调用InputStreamReader中的read方法都会导致从底层输入流读取一个或多个字节,然后调用编码转换器将字节转化为字符。同时为避免频繁调用转换器,实现从字节到字符的高效转换,可以提前从底层流读取更多的字节,以及为了达到最高效率(如:一次读取一行,这也是只有字符流才有行这样的概念),装饰器BufferedReader就这样应运而生了。

 

 

(3)字节流和字符流对比

  • 输出流

 

 

  • 输入流

 至于如何去学习这些IO类的使用,这里还是那句话:看源码、看源码、看源码!我就不一个个方法的贴源码了。但是这里给个大牛整理好的学习路线,传送门

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值