深入理解java IO

一、Java的I/O类库的基本架构


Java的I/O操作类在包java.io下,大概有将近80个类,这些类大概可以分成如下四组。
◎ 基于字节操作的I/O接口:InputStream和OutputStream。
◎ 基于字符操作的I/O接口:Writer和Reader。
◎ 基于磁盘操作的I/O接口:File。
◎ 基于网络操作的I/O接口:Socket。
基于字节的I/O操作接口

image
image

基于字符的I/O操作接口

image
image

字节与字符的转化接口

image

磁盘I/O工作机制
几种访问文件的方式
1.标准访问文件方式

标准访问文件方式就是当应用程序调用read()接口时,操作系统检查内核的高速缓存中有没有需要的数据,如果已经缓存了,那么就直接从缓存中返回,如果没有,从磁盘中读取,然后缓存在操作系统的缓存中。
写入的方式是,用户的应用程序调用write()接口将数据从用户地址空间复制到内核地址空间的缓存中。这时对用户程序来说写操作就已经完成,至于什么时候再写到磁盘中由操作系统决定,除非显式地调用了sync同步命令。

image

2.直接I/O方式

所谓直接I/O方式就是应用程序直接访问磁盘数据,而不经过操作系统内核数据缓冲区,这样做的目的就是减少一次从内核缓冲区到用户程序缓存的数据复制。
这种访问文件的方式通常是在对数据的缓存管理由应用程序实现的数据库管理系统中。
但是直接I/O也有负面影响,如果访问的数据不在应用程序缓存中,那么每次数据都会直接从磁盘加载,这种直接加载会非常缓慢。通常直接I/O与异步I/O结合使用,会得到比较好的性能。

image

3.同步访问文件方式

同步访问文件方式比较容易理解,就是数据的读取和写入都是同步操作的,它与标准访问文件方式不同的是,只有当数据被成功写到磁盘时才返回给应用程序成功标志。
这种访问文件方式性能比较差,只有在一些对数据安全性要求比较高的场景中才会使用,而且通常这种操作方式的硬件都是定制的。

image

4.异步访问文件方式

异步访问文件方式就是当访问数据的线程发出请求之后,线程会接着去处理其他事情,而不是阻塞等待,当请求的数据返回后继续处理下面的操作。
这种访问文件的方式可以明显地提高应用程序的效率,但是不会改变访问文件的效率。

image

5.内存映射方式

内存映射方式是指操作系统将内存中的某一块区域与磁盘中的文件关联起来,当要访问内存中一段数据时,转换为访问文件的某一段数据。
这种方式的目的同样是减少数据从内核空间缓存到用户空间缓存的数据复制操作,因为这两个空间的数据是共享的。

image

Java访问磁盘文件

image

Java序列化技术

 Java序列化就是将一个对象转化成一串二进制表示的字节数组,通过保存或转移这些字节数据来达到持久化的目的。需要持久化,对象必须继承java.io.Serializable接口。反序列化则是相反的过程,将这个字节数组再重新构造成对象。

第一部分是序列化文件头。
◎ AC ED:STREAM_MAGIC声明使用了序列化协议。
◎ 00 05:STREAM_VERSION序列化协议版本。
◎ 73:TC_OBJECT声明这是一个新的对象。
第二部分是要序列化的类的描述,在这里是Serialize类。
◎ 72:TC_CLASSDESC声明这里开始一个新Class。
◎ 00 11:Class名字的长度是17个字节。
◎ 63 6F 6D 70 69 6C 65 2E 53 65 72 69 61 6C 69 7A 65:Serialize的完整类名。
◎ A0 F0 A4 38 7A 3B D1 55: SerialVersionUID,序列化ID,如果没有指定,则会由算法随机生成一个8个字节的ID。
◎ 02:标记号,该值声明该对象支持序列化。
◎ 00 01:该类所包含的域的个数为1。
第三部分是对象中各个属性项的描述。
◎ 49:域类型,49代表“I”,也就是Int类型。
◎ 00 03:域名字的长度,为3。
◎ 6E 75 6D:num属性名称。
第四部分输出该对象的父类信息描述,这里没有父类,如果有,数据格式与第二部分一样。
◎ 78:TC_ENDBLOCKDATA,对象块结束的标志。
◎ 70:TC_NULL,说明没有其他超类的标志。
第五部分输出对象的属性项的实际值,如果属性项是一个对象,那么这里还将序列化这个对象,规则和第二部分一样。
◎ 00 00 05 6E:1390的数值。
虽然Java的序列化能够保证对象状态的持久保存,但是遇到一些对象结构复杂的情况还是比较难处理的,下面是一些复杂对象情况的总结。
◎ 当父类继承Serializable接口,所有子类都可以被序列化。
◎ 子类实现了Serializable接口,父类没有,父类中的属性不能序列化(不报错,数据会丢失),但是子类中属性仍能正确序列化。
◎ 如果序列化的属性是对象,这个对象也必须实现Serializable接口,否则会报错。
◎ 在反序列化时,如果对象的属性有修改或删减,修改的部分属性会丢失,但不会报错。
◎ 在反序列化时,如果serialVersionUID被修改,那么反序列化时会失败。
网络I/O工作机制

image

1.CLOSED:起始点,在超时或者连接关闭时进入此状态。
2.LISTEN:Server端在等待连接时的状态,Server端为此要调用Socket、bind、listen函数,就能进入此状态。这称为应用程序被动打开(等待客户端来连接)。
3.SYN-SENT:客户端发起连接,发送SYN给服务器端。如果服务器端不能连接,则直接进入CLOSED状态。
4.SYN-RCVD:与3对应,服务器端接受客户端的SYN请求,服务器端由LISTEN状态进入SYN-RCVD状态。同时服务器端要回应一个ACK,发送一个SYN给客户端;另外一种情况是,客户端在发起SYN的同时接收到服务器端的SYN请求,客户端会由SYN-SENT到SYN-RCVD状态。
5.ESTABLISHED:服务器端和客户端在完成三次握手后进入状态,说明已经可以开始传输数据了。
6.FIN-WAIT-1:主动关闭的一方,由状态5进入此状态。具体动作是发送FIN给对方。
7.FIN-WAIT-2:主动关闭的一方,接收到对方的FIN ACK,进入此状态。由此不能再接收对方的数据,但是能够向对方发送数据。
8.CLOSE-WAIT:接收到FIN以后,被动关闭的一方进入此状态。具体动作是接收到FIN同时发送ACK。
9.LAST-ACK:被动关闭的一方,发起关闭请求,由状态8进入此状态。具体动作是发送FIN给对方,同时在接收到ACK时进入CLOSED状态。
10.CLOSING:两边同时发起关闭请求时,会由FIN-WAIT-1进入此状态。具体动作是接收到FIN请求,同时响应一个ACK。
11.TIME-WAIT:这个状态比较复杂,也是我们最常见的一个连接状态,有3个状态可以转化为此状态。
◎ 由FIN-WAIT-2转换到TIME-WAIT,具体是:在双方不同时发起FIN的情况下,主动关闭的一方在完成自身发起的关闭请求后,接收到被动关闭一方的FIN后进入的状态。
◎ 由CLOSING转换到TIME-WAIT,具体是:在双方同时发起关闭,都做了发起FIN的请求,同时接收到了FIN并做了ACK的情况下,这时就由CLOSING状态进入TIME-WAIT状态。
◎ 由FIN-WAIT-1转换到TIME-WAIT,具体是:同时接收到FIN(对方发起)和ACK(本身发起的FIN回应),它与CLOSING转换到TIME-WAIT的区别在于本身发起的FIN回应的ACK先于对方的FIN请求到达,而CLOSING转换到TIME-WAIT是FIN先到达。

影响网络传输的因素

◎ 网络带宽
◎ 传输距离
◎ TCP拥塞控制

Java Socket的工作机制
image

NIO的工作方式

BIO即阻塞I/O,不管是磁盘I/O还是网络I/O,数据在写入OutputStream或者从InputStream读取时都有可能会阻塞,一旦有阻塞,线程将会失去CPU的使用权,这在当前的大规模访问量和有性能要求的情况下是不能被接受的。
image

两个关键类:Channel和Selector,它们是NIO中的两个核心概念。
我们仍用前面的城市交通工具来继续比喻NIO的工作方式,这里的Channel要比Socket更加具体,它可以比做某种具体的交通工具,如汽车或高铁,而Selector可以比做一个车站的车辆运行调度系统,它将负责监控每辆车的当前运行状态,是已经出战,还是在路上等。
也就是它可以轮询每个Channel的状态。这里还有一个Buffer类,它也比Stream更加具体,我们可以将它比做车上的座位。
Channel是汽车的话Buffer就是汽车上的座位,它始终是一个具体的概念,与Stream不同,Stream只能代表一个座位,至于是什么座位由你自己去想象,也就是你在上车之前并不知道这个车上是否还有没有座位,也不知道上的是什么车,因为你并不能选择。
而这些信息都已经被封装在了运输工具(Socket)里面,对你是透明的。NIO引入了Channel、Buffer和Selector就是想把这些信息具体化,让程序员有机会控制它们。
例如,当我们调用write()往SendQ中写数据时,当一次写的数据超过SendQ长度时需要按照SendQ的长度进行分割,这个过程中需要将用户空间数据和内核地址空间进行切换,而这个切换不是你可以控制的,但在Buffer中我们可以控制Buffer的容量、是否扩容以及如何扩容。

Buffer
image

NIO的数据访问方式
NIO提供了比传统的文件访问方式更好的方法,NIO有两个优化方法:一个是FileChannel.transferTo、FileChannel.transferFrom;另一个是FileChannel.map。

    1.FileChannel.transferXXX
    
    FileChannel.transferXXX与传统的访问文件方式相比可以减少数据从内核到用户空间的复制,数据直接在内核空间中移动,在Linux中使用sendfile系统调用。
    图2-20是传统的数据访问方式,图2-21是FileChannel.transferXXX的访问方式。

image

    2.FileChannel.map

    FileChannel.map将文件按照一定大小块映射为内存区域,当程序访问这个内存区域时将直接操作这个文件数据,这种方式省去了数据从内核空间向用户空间复制的损耗。

I/O调优
磁盘I/O优化

    1.性能检测
    压力测试应用程序看系统的I/O wait指标是否正常,例如,测试机器有4个CPU,那么理想的I/O wait参数不应该超过25%,如果超过25%,I/O很可能成为应用程序的性能瓶颈。Linux操作系统下可以通过iostat命令查看。
    通常我们在判断I/O性能时还会看另外一个参数,就是IOPS,应用程序需要的最低的IOPS是多少,磁盘的IOPS能不能达到要求。每个磁盘的IOPS通常在一个范围内,这和存储在磁盘上的数据块的大小和访问方式也有关,但主要是由磁盘的转速决定的,磁盘的转速越高磁盘的IOPS也越高。
    现在为了提高磁盘I/O的性能,通常采用一种叫RAID的技术,就是将不同的磁盘组合起来以提高I/O性能,目前有多种RAID技术,每种RAID技术对I/O性能的提升会有不同,可以用一个RAID因子来代表,磁盘的读写吞吐量可以通过iostat命令来获取,于是可以计算出一个理论的IOPS值,计算公式如下:
    (磁盘数×每块磁盘的IOPS)/(磁盘读的吞吐量+RAID因子×磁盘写的吞吐量)=IOPS
    
    2.提升I/O性能

    通常提升磁盘I/O性能的方法有:
    ◎ 增加缓存,减少磁盘访问次数。
    ◎ 优化磁盘的管理系统,设计最优的磁盘方式策略,以及磁盘的寻址策略,这是在底层操作系统层面考虑的。
    ◎ 设计合理的磁盘存储数据块,以及访问这些数据块的策略,这是在应用层面考虑的。例如,我们可以给存放的数据设计索引,通过寻址索引来加快和减少磁盘的访问量,还可以采用异步和非阻塞的方式加快磁盘的访问速度。
    ◎ 应用合理的RAID策略提升磁盘IO,RAID策略及说明如表2-3所示。

image

TCP网络参数调优

Linux中可以通过查看/proc/sys/net/ipv4/ip_local_port_range文件来知道当前这个主机可以使用的端口范围。
如果可以分配的端口号偏少,在遇到大量并发请求时就会成为一个瓶颈。
除了增大端口范围之外,还可以让TCP连接复用等,这些调优参数如表2-4所示。

image

另外,Linux还提供了一些工具可以查看当前的TCP统计信息,如下所示。
◎ cat /proc/net/netstat:查看TCP的统计信息。
◎ cat /proc/net/snmp:查看当前系统的连接情况。
◎ netstat -s:查看网络的统计信息。

网络I/O优化

减少网络交互的次数。
减少网络传输数据量的大小。
尽量减少编码。
根据应用场景设计合适的交互方式。
    1.同步与异步

    所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能完成,这是一种可靠的任务序列。
    要成功都成功,要失败都失败,两个任务的状态可以保持一致。而异步不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。
    至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。我们可以用打电话和发短信来很好地比喻同步与异步操作。
    
    2.阻塞与非阻塞

    阻塞与非阻塞主要是从CPU的消耗上来说的,阻塞就是CPU停下来等待一个慢的操作完成以后,CPU才接着完成其他的事。
    非阻塞就是在这个慢的操作执行时CPU去干其他别的事,等这个慢的操作完成时,CPU再接着完成后续的操作。
    虽然表面上看非阻塞的方式可明显地提高CPU的利用率,但是也带了另外一种后果,就是系统的线程切换增加。增加的CPU使用时间能不能补偿系统的切换成本需要好好评估
    。
    
    3.两种方式的组合

image

猜你喜欢

转载自blog.csdn.net/zjltju1203/article/details/89277785