NIO浅析

1、什么是NIO?

在 JDK1.4 之后,为了提高 Java IO 的效率,Java 又提供了一套 New IO(NIO),原因在于它相对于之前的 IO 类库是新增的。此外,旧的 IO 类库提供的 IO 方法是阻塞的,New IO 类库则让 Java 可支持非阻塞 IO,所以,更多的人喜欢称之为非阻塞 IO(Non-blocking IO)

2、为什么要用NIO?

所有的系统 I/O 都分为两个阶段:等待就绪和操作。举例来说,读函数,分为等待系统可读和真正的读;同理,写函数分为等待网卡可以写和真正的写。Java IO 的各种流是阻塞的。这意味着当线程调用 write() 或 read() 时,线程会被阻塞,直到有一些数据可用于读取或数据被完全写入。
传统的阻塞型IO大致有如下几个缺点:

  1. 线程不够用, 就算使用了线程池复用线程也无济于事; 
  2. 阻塞I/O模式下,会有大量的线程被阻塞,一直在等待数据,这个时候的线程被挂起,只能干等,CPU利用率很低,换句话说,系统的吞吐量差; 
  3. 如果网络I/O堵塞或者有网络抖动或者网络故障等,线程的阻塞时间可能很长。整个系统也变的不可靠;

最典型的在实际生产环境中会遇到的,就是socket.read()/socket.accept()的阻塞造成的问题。当一个线程执行 ServerSocket 的accept() 方法时, 假如没有客户连接, 该线程就会一直等到有客户连接才从 accept() 方法返回. 再例如, 当线程执行 Socket 的 read() 方法时, 如果输入流中没有数据, 该线程就会一直等到读入足够的数据才从 read() 方法返回

四种 IO 模型
同步阻塞 IO:
在此种方式下,用户进程在发起一个 IO 操作以后,必须等待 IO 操作的完成,只有当真正完成了 IO 操作以后,用户进程才能运行。 Java 传统的 IO 模型属于此种方式!

同步非阻塞 IO:
在此种方式下,用户进程发起一个 IO 操作以后 便可返回做其它事情,但是用户进程需要时不时的询问 IO 操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的 CPU 资源浪费。其中目前 Java 的 NIO 就属于同步非阻塞 IO 。

异步阻塞 IO:
此种方式下是指应用发起一个 IO 操作以后,不等待内核 IO 操作的完成,等内核完成 IO 操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问 IO 是否完成,那么为什么说是阻塞的呢?因为此时是通过 select 系统调用来完成的,而 select 函数本身的实现方式是阻塞的,而采用 select 函数有个好处就是它可以同时监听多个文件句柄,从而提高系统的并发性!

异步非阻塞 IO:
在此种模式下,用户进程只需要发起一个 IO 操作然后立即返回,等 IO 操作真正的完成以后,应用程序会得到 IO 操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的 IO 读写操作,因为 真正的 IO 读取或者写入操作已经由 内核完成了。目前 Java 中还没有支持此种 IO 模型。

3、NIO和BIO对比:

BIO是面向流的,不存在缓存的概念。Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。
Java IO的各种流是阻塞的,这意味着当一个线程调用read或 write方法时,该线程被阻塞,直到有一些数据被读取,或数据完全写入,该线程在此期间不能再干任何事情了。

NIO是面向缓冲区的。这个缓冲区分为“直接缓冲区”和“非直接缓冲区"。
直接缓冲区:由Buffer.allocate(int capacity)创建在jvm内存中的。
非直接缓冲区:由Buffer.allocateDirect(int capacity)创建,在JVM内存外开辟内存,直接缓冲区的性能很高,但是因为他的内存是操作系统分配的,绕过了jvm堆栈的机制,建立和销毁的开销比较大。
NIO是非阻塞的。他可以使得一个线程从某通道发送读取数据的请求时,读取目前可用的数据,如果没有可用数据,就什么都不会获取,而不是保持阻塞,该线程可以做其他的事情。

NIO是可以做到用一个线程来处理多个操作的。假设有10000个请求过来,根据实际情况,可以分配50或者100个线程来处理。不像之前的阻塞IO那样,非得分配10000个。

4、怎么使用NIO?

NIO的核心API:Buffer(缓存)、Channel(通道)、Selector(选择器)
缓存Buffer:
Buffer接口的实现有ByteBuffer、CharBuffer、IntBuffer、ShortBuffer、LongBuffer、DoubleBuffer、FloatBuffer。分别对应各自的基本数据类型。还有MappedByteBuffer、HeapByteBuffer、DirectByteBuffer等等。
Buffer的使用步骤一般是:
1、初始化Buffer。
2、写入数据到Buffer。
3、调用Buffer.flip(),把Buffer切换为读模式。
4、从Buffer中读取数据。
5、调用clear()方法或者compact()方法。清空整个缓冲区(clear()方法),或者清空已经读过的数据(compact())

package study.NIO;

import java.nio.CharBuffer;

public class BufferTest {
    public static void main(String[] args) {
        CharBuffer buffer = CharBuffer.allocate(8);
        System.out.println("|"+buffer.position()+"|");
        System.out.println("|"+buffer.capacity()+"|");
        System.out.println("|"+buffer.limit()+"|");
        System.out.println("|"+buffer.length()+"|");
        buffer.append("abc");
        buffer.put("def");
        System.out.println("---------------");
        System.out.println("|"+buffer.position()+"|");
        System.out.println("|"+buffer.capacity()+"|");
        System.out.println("|"+buffer.limit()+"|");
        System.out.println("|"+buffer.length()+"|");
        buffer.flip();
        System.out.println("---------------");
        System.out.println("|"+buffer.position()+"|");
        System.out.println("|"+buffer.capacity()+"|");
        System.out.println("|"+buffer.limit()+"|");
        System.out.println("|"+buffer.length()+"|");
        buffer.compact();
        System.out.println("---------------");
        System.out.println("|"+buffer.position()+"|");
        System.out.println("|"+buffer.capacity()+"|");
        System.out.println("|"+buffer.limit()+"|");
        System.out.println("|"+buffer.length()+"|");
        buffer.clear();
        System.out.println("---------------");
        System.out.println("|"+buffer.position()+"|");
        System.out.println("|"+buffer.capacity()+"|");
        System.out.println("|"+buffer.limit()+"|");
        System.out.println("|"+buffer.length()+"|");
        System.out.println("|"+buffer.get(1)+"|");

    }
}

结果:

|0|
|8|
|8|
|8|
---------------
|6|
|8|
|8|
|2|
---------------
|0|
|8|
|6|
|6|
---------------
|6|
|8|
|8|
|2|
---------------
|0|
|8|
|8|
|8|
|b|

通过最后一个buffer.get(1)可以看到,执行clear()/compact(),并不是真正的清除,而是把position、limit重新设置一下。这一点可以从源码得到。

    /**
     * Clears this buffer.  The position is set to zero, the limit is set to
     * the capacity, and the mark is discarded.
     *
     * 

Invoke this method before using a sequence of channel-read or * put operations to fill this buffer. For example: * *

     * buf.clear();     // Prepare buffer for reading
     * in.read(buf);    // Read data
* *

This method does not actually erase the data in the buffer, but it * is named as if it did because it will most often be used in situations * in which that might as well be the case.

* * @return This buffer */ public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; }

 

你可能感兴趣的:(java编程,java,nio,IO)