聊聊JAVA中的I/O

2019-12-25  本文已影响0人  GeorgeDon

I/O即为输入输出的意思,在实际应用中的I/O操作主要是针对磁盘或者网络。磁盘的I/O主要是针对文件读写,而网络I/O是针对socket中的数据的读写。

I/O的原理

计算机资源都是由操作系统控制的,jvm通过系统调用去实现数据的读写。操作系统读取数据的时候首先会将数据读取到内核的缓冲区,然后再从内核读取到应用控制的内存中。当要写的数据输出也是先将数据写入到内核在写入磁盘或者网卡。这样的好处是对磁盘的读取可以缓存起来,当其他的应用需要读取同样的数据的时候就避免了磁盘的操作,用户与内核隔离这样,用户修改了数据不会影响内核。

linuxIO.png

当然linux还提供了mmap技术,可以参考linux内存映射mmap原理分析。mmap技术是将应用的内存映射到磁盘上面,当用户要读取数据的时候直接从磁盘读取到内存中,当将这段内存的数据进行修改之后,操作系统会将这个修改同步到磁盘上,mmap的好处就是可以读取或者写数据的时候可以减少一次数据拷贝,提升速度。还有就是零拷贝技术,就是在进行文件复制传输的时候,同样不需要讲数据拷贝到用户的进程中,而是直接在内核中完成晚间的传输。(java nio的 FileChannel.transferTo()/transferFrom() ,就是这种模式)。

mmap.png

mmap虽然好但是有缺点:

  1. mmap映射是按照页映射的,如果是大量小文件映射会造成内存的浪费

  2. 不再安全,应用对内存的修改将直接影响磁盘上的数据,如果有其他的应用读取该文件,将受到影响

java传统的I/O操作

传统的java的I/O操作都是基于流进行的,主要为基于字节的InputStream、OutputStream和基于字符操作的Reader和Writer。每当读取一个字节或者字符的时候就进行一次系统调用,从磁盘上读取一个字节或字符进入内存,这样操作的效率是十分低下的。所以java提供了一直 Bufferd+*( BufferdInputStream、BufferdReader等)的操作,就是在之前的基础上增加了一个缓冲区,每次读取都先读取一大块数据到缓冲区再从缓冲区拷贝到应用内存中,写数据一样先写入缓冲区再一起写入磁盘或者网络,这样就可以提升读写的效率。

而cpu的处理速度和内存的读写速度是远远大于磁盘和网络数据的读写,传统的io是阻塞的,每当读取数据的时候,当前线程就会被阻塞。假如网络上有很多客户端连接本地,就需要多少个线程,这样是很消耗计算机资源的,这就需要非阻塞的IO。

linux中的I/O模型

  1. 阻塞I/O:进程通过系统调用读取数据,当数据成功读取到进程内存中的时候才返回

  2. 非阻塞I/O:进程通过系统调用读取数据,这个时候如果内核中缓冲区没有数据就直接返回并告知进程内核现在在准备数据,此时进程就可以处理其他事情,然后随时去查询内核准备数据的状态,如果准备ok就从内核拷贝过来,内存的读写速度是比磁盘快很多,这个时候的拷贝其实是很快的。

  3. I/O复用模型:通过一个线程处理多个I/O操作,基于select/poll系统,将多个非阻塞的I/O交给该系统,该系统不断去轮询每个I/O的状态,如果ok就处理这个I/O的数据。select/poll的轮询机制存在缺陷,后面升级为基于事件的epoll的模式。

  4. 信号驱动I/O:进程通过系统调用读取数据,马上返回,然后内核数据准备好之后通过信号量通知进程数据准备好了,不需要进程来主动查询信号状态。

  5. 异步I/O:和信号驱动一样,调用之后等待系统的信号,但是这个时候数据已经完全写入内存了,无需进程再从内核读取出来。

java NIO

NIO是基于channel操作的,这个channel是双向的,可以读也可以写。并且提供了上述提到的对Buffter(缓冲区)的操作。通过将channel注册到selectors上实现非阻塞的读写,selectors是基于上述提到的epoll模式,当selectors感知到channel数据准备ok就对数据进行读写的操作。

Netty

直接使用JAVA NIO进行网络编程开发是费时费劲的,Netty是基于Java NIO 实现的网络编程框架,使用Netty编写高并发的网络服务应用。

参考

  1. 《Netty权威指南》
上一篇下一篇

猜你喜欢

热点阅读