摘要:IO问题可以说是当今web应用中面临的主要问题之一。因为在这个数据爆发的时代,海量的数据在网络到处流动,而在这个过程中都会涉及IO问题,可以说IO问题已经成为web应用的瓶颈之一。如何优化?以此提高效率,了解IO的工作机制就显得尤为重要了。

一、概述

  java的io类库在java.io包下,大概将近80个类,按照功能大致可以分为一下4组:

    • 基于字节操作的IO接口:InputStream/OutputStream
    • 基于字符操作的IO接口:Writer/Reader
    • 基于磁盘操作的IO接口:File
    • 基于网络操作的IO接口:Socket

  前两组按照数据传输的格式划分,后两组按照数据传输的方式划分。(虽然Socket不是java.io包下类库,但是它和数据的传输方式息息相关,因此在此一起分析),输入根据操作类型和操作方式又被分为若干个子类。由于本篇不是讲解IO的具体操作,而是站在更高的抽象位置分析java IO 的工作机制,所以就不讲如何使用了(后续学习源码了之后再分享)。但是需要说明的是,java IO 不仅仅是操作本地磁盘的文件,也可以把网络传输作为数据的目的地。不管是磁盘传输还是网络传输,最小的存储单元都是字节,而不是字符。因此java IO操作的也是字节,之所以有字符的操作接口,完全是为了方便我们在程序中的使用,因为我们平时处理的多是字符类型的数据。底层存储的依然是以字节为单位。由于字节到字符的的转换涉及到编码问题,所以我们在使用的过程中一定注意字符编码的问题。下面就分别以java本地IO和网络传输IO分析其原理和工作机制。

二、磁盘I/O工作机制

2.1 从操作系统讲起——程序访问文件的几种方式

  程序关于IO的操作——读写文件都是都是通过系统调用的方式来工作的,因为磁盘设备是由操作系统(OS)维护的,读写文件通过系统调用的read()和write()方式,但是只要是系统调用,就会存在内核空间和用户空间地址的转换和数据的复制,如果IO请求很大的时候,就会造成系统缓慢,为此操作系统引入了缓存的概念。以此提高IO的响应时间。那么访问文件的方式有哪些呢?一一道来。

a)标准访问文件的方式

  当程序调用read() 接口时,OS检查缓存中是否有目标数据,有直接返回,没有的话从磁盘读取并放入缓存。write()的时候,OS将数据从用户空间复制到内核空间的缓存中,此时对于用户来说写入已经完成,接下来由OS决定何时持久化,如需立即同步,显示调用sync;

b)直接IO的方式

  程序直接访问磁盘数据而不经过OS内核的高速缓存,减少了用户空间到内核空间的复制,这样的应用的程序通常是自身控制数据的缓存机制,它自己知道哪些数据应该内缓存,哪些不用。甚至可以提前把热点数据加载到内存中,以此来提高访问的效率。典型的实现是数据库管理系统。

c)同步访问文件的方式

  数据的读取和写入都是同步的,它与标准方式访问文件不同的是,只有当数据成功持久化到磁盘后才返回,这样大大的降低了系统性能只有对数据安全性考虑较高的情况下使用。

d)异步访问文件的方式

  当数据访问的线程发出后,线程转而去做其他的事情,而不是阻塞等待。这种方式可以明显的提高系统的效率,但是不能根本的解决数据访问的问题。

e) 内存映射访问的方式

  OS将内存中的某一块区域与磁盘的文件相关联起来,将访问内存中的数据转换为访问文件的数据,这种方式减少了数据复制的操作。

2.2 java访问磁盘文件

  java访问磁盘文件都是基于java.io包下定义的对数据操作的接口,以一个程序从磁盘读取一段文本字符为例,说明java访问磁盘数的过程:当传入一个文件路径时,根据这个路径来创建一个File对象来标识这个文件,然后根据这个File对象创建真正读取文件的操作对象,这时会创建一个和真实磁盘文件关联的文件描述符FileDescriptor对象,通过这个文件描述符对象可以直接控制磁盘文件,由于我们读取的是字符格式,因此需要一个StreamDecoder对象来将byte转换为char。再往下就是如何从磁盘驱动器访问数据了,这一步由操作系统完成,不同的操作系统有不同的实现。

代码示例:

public class ReadFileDemo {

    public static void main(String[] args) {

        // 根据传入的路径 创建一个File对象来标识这个文件
File file = new File("D:/test.txt"); // 缓存数组,防止文件过大
char[] c = new char[1024]; Reader reader = null;
StringBuffer str = new StringBuffer(); try {
/**
* 当我们创建 FileInputStream 对象真正操作文件时,才真正关心文件的有效性 因此抛出运行时异常 FileNotFoundException
* 根据File对象创建一个真正操作文件的对象,此时会创建一个FileDescriptor文件描述符,可以直接操作磁盘文件
*/
// 由于我们访问的是字符格式数据,因此此时会创建一个StreamDesciptor对象转换数据格式
reader = new InputStreamReader(new FileInputStream(file));
// 真正的开始读取文件时。也会抛出 IOException
while (reader.read(c) != -1) { // 通过 FileDescriptor 访问文件
str.append(c);
} } catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (reader != null)
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
} System.out.println(str); } }

2.3 java序列化技术

  java序列化是将一个对象转换为二进制表示的字节数组,通过保存或者转移达到对象持久化的目的。某个java对象需要序列化,那么需要实现java.io.Serializable 接口,反序列化则是相反的过程,但是我们需要有原始类作为模板,才能将对象反序列化。但我们在应用中使用序列化技术时,需要注意几个问题:

    • 当父类继承了Serializable 接口,那么它的子类都可以被序列化。
    • 当子类实现了Serializable 接口,但是父类没有实现时,父类的属性不能被序列化(不会报错,但是数据会丢失),子类属性可以正常序列化。
    • 如果序列化的属性是对象,那么这个对象也必须实现Serializable 接口,否则会报错。
    • 在反序列化时,如果对象的属性有变化或者修改,则修改的部分属性会丢失,但是不会报错。
    • 在反序列化时,如果序列化的ID修改了,那么反序列化也会失败。

  在纯java环境中,序列化可以很好地工作,但是在多语言环境下,java序列化的对象用其他语言很难还原,因此尽量使用通用的数据结构,如json,xml,或者其他的序列化技术。

三、网络IO工作机制

  数据从一台主机发送到网络中的另一台主机的过程,首先需要和目标主机建立起连接,和使用相同的通信协议和交流语言,才能完成数据的传输。因此在介绍java 中Socket传输数据之前,需要先讨论一下网络。如何建立和关闭一个TCP连接,TCP的连接状态转换有哪些?

3.1 TCP连接状态转换

序号 状态 描述
1 CLOSED 起始点,连接超时或者连接关闭时,进入此状态。
2 LISTEN Server端在等待连接时的状态,Server端为此要调用Socket,bind,listen。称为被动打开。
3 SYN-SENT Client发送SYN给Server,如果不能连接那么直接进入CLOSED状态。
4 SYN-RCVD 与3对应,Server端接收Client的SYN,由LISTEN变为此状态,并给Client回应一个 ACK,一个SYN.另外一种情况是Client在等待Server的ACK的时候,同时受到Server的SYN,那么Client也会进入此状态。
5 ESTABLISHED Server与Client在完成三次握手后进入的状态,此时说明已经可以传输数据了。
6 FIN-WAIT-1 主动发起关闭的一方,由状态5进入此状态,具体动作是发送FIN给对方
7 FIN-WAIT-2 主动关闭的一方,在收到对方FIN-ACK后进入此状态。此时不能再接收对方的数据,但是可以向对方发送数据
8 CLOSED-WAIT 收到FIN后,被动关闭的一方进入此状态,动作是收到FIN的同时给对方回应ACK
9 LAST-ACK 被动关闭的一方,发起关闭请求,由8进入此状态,动作是发送FIN给对方,同时在接收到ACK时进入CLOSED状态
10 CLOSING 两边同时发起关闭请求时,会由FIN-WAIT-1进入此状态,动作是收到FIN请求,同时响应一个ACK.
11 TIME-WAIT 可由三种状态转换为此状态。

哪几种情况会让TCP连接进入TIME-WAIT状态?

  1. 双方不同时发起FIN的情况下,由FIN-WAIT-2 转换为TIME-WAIT,主动关闭的一方在完成自身发起的请求后,接收到被动一方的FIN后进入的状态。

  2. 双方同时关闭的情况下,由CLOSING转换到TIME-WAIT,双方都发起了FIN,同时接收到了FIN并回应ACK的情况下,进入TIME-WAIT状态。

  3. 同时收到FIN和ACK,它与上面情况不同的是本身发起的FIN回应的ACK先于对方的FIN请求到达。

3.2 影响网络传输的因素

  将一台主机中的数据传到网络中的另一台主机锁需要的时间我们叫做响应时间,影响这个响应时间的因素有很多,常见的有如下几种:

a).网络带宽:带宽就是一条物理链路在1s内能够传输的最大比特数。注意是比特,而不是字节。

b).传输距离:传输的距离,国内和国外肯定是不一样的。

c).TCP拥堵控制

四、java Socket的工作机制

  Socket的概念没有对应到一个具体的实体,它描述的是计算机之间相互通信的一种抽象功能。一般情况下,我们使用的 Socket都是基于TCP/IP的流套接字,它是一种稳定的通信协议。下图是一个典型的基于Socket通信的场景。

  上图中主机A要和主机B建立通信,必须通过Socket建立通信,而在Socket连接底层是基于TCP/IP的TCP连接,TCP连接通过IP在网络中寻找目标主机,主机上不同的应用程序又通过本地端口号来区分,那么基于套接字(192.168.10.2:8080)这样的ip:port 创建的唯一Socket就代表主机上一个应用程序的通信链路了。

4.1 建立通信链路

  当Client需要与Server通信的时候,Client首先建立一个指定套接字的Socket示例,此时操作系统将为这个Socket示例分配一个包含本地地址,远程地址,端口号的数据结构,这个数据结构将一直保存到当前连接关闭。在创建Socket示例的构造函数正确返回前,会进行TCP的3次握手协议,握手完成后,正确返回Socket,否则将抛出异常IOException;

  与之对应的Server将创建一个ServerSocket示例,创建ServerSocket比较简单,只要保证指定监听的端口号没有被占用,一般都没有问题。同样的操作系统也会其分配一个数据结构(但是不同的是,在没有Client连接之前,该数据结构只包含了监听的端口号和匹配的地址符)。然后调用accept()进入阻塞状态,等待Client的连接;当Client请求的时候,此时操作系统为该连接实例分配一个数据结构并放在未完成的数据结构列表中,注意,此时的套接字数据结构并没有完成,还需要等到Client的3次握手完成后,Server端的Socket实例才能返回,并从未完成的列表中移动到已经完成的列表中。所以ServerSocket关联的列表中,每一个数据结构都代表了一个Client的连接实例。

4.2 数据传输

  建立连接的目的是传输数据,那么连接成功后,数据传输的基本流程和方式是在怎么样的呢?

  当连接成功后,Server和Client都有都会拥有一个Socket实例,每个Socket实例都有一个InputStream和OutputStream,并通过通过这两个对象来交换数据。同时网络IO是以字节流来传输数据的,当创建Socket对象时,系统会为InputStream和OutputStream分配一个缓冲区,数据的写入和读取都是通过这个缓冲区完成的。写入端将数据写入到OutputStream的SendQ队列中,当队列满时,数据将被转移到另一端InputStream的RevcvQ中,如果这个RecvQ也满了,那么OutputStream将会的write方法将会阻塞,知道RecvQ队列有空闲的空间容纳SendQ发送的数据。需要注意的是,这个缓冲区的大小及写入端和读取端的速度直接影响了这个连接的传输效率,由于可能会发生阻塞,所以网络IO和磁盘IO最大的不同就是数据的写入和读取还需要一个协调的过程。如果两边同时传输数据就很可能会造成死锁。NIO解决了数据传输阻塞的问题。

[I/O]javaI/O工作机制的更多相关文章

  1. android 6.0 高通平台sensor 工作机制及流程(原创)

    最近工作上有碰到sensor的相关问题,正好分析下其流程作个笔记. 这个笔记分三个部分: sensor硬件和驱动的工作机制 sensor 上层app如何使用 从驱动到上层app这中间的流程是如何 Se ...

  2. Java IO工作机制分析

    Java的IO类都在java.io包下,这些类大致可分为以下4种: 基于字节操作的 I/O 接口:InputStream 和 OutputStream 基于字符操作的 I/O 接口:Writer 和 ...

  3. malloc 函数工作机制(转)

    malloc()工作机制 malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表.调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块.然后,将 ...

  4. springMVC工作机制和框架搭建配置说明

    先说一下springMVC的工作机制 1.springmvc把 所有的请求都提交给DispatcherServlet,它会委托应用系统的其他模块负责对请求进行真正的处理工作. 2.Dispatcher ...

  5. CKPT进程工作机制

    CKPT进程工作示意图 2.CKPT进程工作机制 检查点进程被触发的条件为: a> 当发生日志组切换时: b>  用户提交了事务时(commit): c>  Redo log buf ...

  6. 15 sql base line 工作机制

    <个人Configuration> 正常配置一下, 就OK了, 不用理了, oracle 11g 默认启动 发展: .从Oracle的发展角度来看,估计这种方法是Oracle发展和改进的方 ...

  7. HBase读写路径的工作机制

    出处:http://wuyudong.com/1946.html HBase 写路径工作机制 在HBase 中无论是增加新行还是修改已有的行,其内部流程都是相同的.HBase 接到命令后存下变化信息, ...

  8. PHP自动加载__autoload的工作机制

    PHP自动加载__autoload的工作机制 PHP的懒加载lazy loading 在 2011年11月12日 那天写的     已经有 4559 次阅读了 感谢 参考或原文   服务器君一共花费了 ...

  9. hadoop MapReduce 工作机制

    摸索了将近一个月的hadoop , 在centos上配了一个伪分布式的环境,又折腾了一把hadoop eclipse plugin,最后终于实现了在windows上编写MapReduce程序,在cen ...

随机推荐

  1. 枚举工具类 EnumUtils

    这是一个枚举工具类,有时候一个枚举自身为空,我们程序在调用他的方法时(如:name(),ordinal()),就会报空指针异常:下面写这个工具类就是为了来避免这些问题的,有些外部引用的没有贴出来,不过 ...

  2. LeetCode-Evaluate Reverse Polish Notation[AC源码]

    package com.lw.leet2; /** * @ClassName:Solution * @Description: * Evaluate the value of an arithmeti ...

  3. K. Random Numbers(Gym 101466K + 线段树 + dfs序 + 快速幂 + 唯一分解)

    题目链接:http://codeforces.com/gym/101466/problem/K 题目: 题意: 给你一棵有n个节点的树,根节点始终为0,有两种操作: 1.RAND:查询以u为根节点的子 ...

  4. Sublime快捷键(一)

    最近在工作中,遇到的sublime的快捷键,以后再工作中用到的我会稍后增加的~ 快捷键: 1.切换标签页: Ctrl + Tab    切换标签页: Ctrl + Shift + Tab   返回刚切 ...

  5. 使用maven打包项目遇到错误: http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException

    今天在使用maven打包项目时遇到一个错误: [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin ...

  6. 解决word2016鼠标每点击一下就出现一个保存的圆圈

    问题描述:今天打开word2016时,点击鼠标,随着鼠标会出现一个圆圈,让人看着很不习惯,通过查阅资料和亲自实践,记录在博客中. 由于自己之前装了PowerDesigner,PowerDesigner ...

  7. php7.33 configure

    To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for desc ...

  8. 如何删除git远程分支(转)

    1,在开发过程中,大家在远程创建了许多分支,有些是无用的,该如何删除呢,可以参考下面的方法. 如果不再需要某个远程分支了,比如搞定了某个特性并把它合并进了远程的 master 分支(或任何其他存放 稳 ...

  9. Oracle数据库(64位) 及 PLSQL(64位)的组合安装【第一篇】

    目前PC端普遍使用64位操作系统,64位操作系统在性能上比32位更佳,但是兼容性上则不如32的操作系统,Oracle提供了64和32两种版本,而PLsql工具则只有32位,在这篇文章详细的讲述了如何在 ...

  10. cvc-complex-type.2.4.a: Invalid content was found starting with element ‘init-param’(转)

    在写xml的时候又一次总是报cvc-complex-type.2.4.a: Invalid content was found starting with element 错误,还出现小红叉,在网上找 ...