I/O 问题可以说是当今互联网 Web 应用中所面临的主要问题之一,因为当前在这个海量数据时代,数据在网络中随处流动。这个流动的过程中都涉及到I/O 问题,可以说大部分 Web 应用系统的瓶颈都是 I/O 瓶颈。今天,我们先来简单讲讲磁盘IO的工作机制。
在讲之前,我们先来看看Java的I/O类库的基本架构:
Java的I/O类库的基本架构:
I/O 问题是任何编程语言都无法回避的问题,可以说 I/O 问题是整个人机交互的核心问题,因为 I/O 是机器获取和交换信息的主要渠道。在当今这个数据大爆炸时代,I/O 问题尤其突出,很容易成为一个性能瓶颈。正因如此,所以 Java 在 I/O 上也一直在做持续的优化,如从 1.4 开始引入了 NIO,提升了 I/O 的性能。关于 NIO 我们将在以后详细介绍。
Java 的 I/O 操作类在包 java.io 下,大概有将近 80 个类,但是这些类大概可以分成四组,分别是:
- 基于字节操作的 I/O 接口:InputStream 和 OutputStream
- 基于字符操作的 I/O 接口:Writer 和 Reader
- 基于磁盘操作的 I/O 接口:File
- 基于网络操作的 I/O 接口:Socket
前两者主要根据传输数据的数据格式,后两组主要是根据传输数据的方式,虽然 Socket 类并不在 java.io 包下,但是我仍然把它们划分在一起,因为我个人认为 I/O 的核心问题要么是数据格式影响 I/O 操作,要么是传输方式影响 I/O 操作,也就是将什么样的数据写到什么地方的问题,I/O 只是人与机器或者机器与机器交互的手段,除了在它们能够完成这个交互功能外,我们关注的就是如何提高它的运行效率了,而数据格式和传输方式是影响效率最关键的因素了。我们后面的分析也是基于这两个因素来展开的。
基于字节的 I/O 操作接口:
基于字节的 I/O 操作接口输入和输出分别是:InputStream 和 OutputStream,InputStream 输入流的类继承层次如下
图所示:
输入流根据数据类型和操作方式又被划分成若干个子类,每个子类分别处理不同操作类型,OutputStream 输出流的类层次结构也是类似,如下图所示:
这里就不详细解释每个子类如何使用了,如果不清楚的话可以参考一下JDK的API说明文档,这里只想说明两点,一个
是操作数据的方式是可以组合使用的,如这样组合使用。
OutputStream out = new BufferedOutputStream(new ObjectOutputStream(new FileOutputStream("fileName"))
;
还有一点是流最终写到什么地方必须要指定,要么是写到磁盘要么是写到网络中,其实从上面的类图中我们发现,写网
络实际上也是写文件,只不过写网络还有一步需要处理就是底层操作系统再将数据传送到其它地方而不是本地磁盘。关于
网络 I/O 和磁盘I/O我们将在以后详细介绍。
基于字符的 I/O 操作接口:
len)由子类去实现。
Writer 相关类层次结构:
读字符的操作接口也有类似的类结构,如下图所示:
读字符的操作接口中也是int read(char cbuf[], int off, int len),返回读到的 n 个字节数,不管是Writer还是
Reader类它们都只定义了读取或写入的数据字符的方式,也就是怎么写或读,但是并没有规定数据要写
到哪去,写到哪去就是我们后面要讨论的基于磁盘和网络的工作机制。
字节与字符的转化接口:
需要转化,其中读的转化过程如下图所示:
字符解码相关类结构:
将采用操作系统默认字符集,很可能会出现乱码问题。StreamDecoder 正是完成字节到字符的解码的实现类。
也就是当你用如下方式读取一个文件时:
在介绍Java读取和写入磁盘文件之前,先来看看应用程序访问文件有哪几种方式。
Java读写磁盘:
下面介绍5种访问文件的方式:
1.标准访问文件的方式
2.直接I/O的方式
3.同步访问文件的方式
4.异步访问文件的方式
5.内存映射的方式
现在还有一个关键问题那就是数据写到何处,其中一个主要的方式就是将数据持久化到物理磁盘。
Java从磁盘读取文件的流程:
序列化:
简单地讲就是将一个对象转换成一串二进制表示的字节数组,通过保存或转移这些字节数据来达到持久化目的。需要
达到持久化,必须继承java.io.Serializable接口。
当然,序列化有很多不足的地方,下面是一些复杂情况下的问题: