在 JDK1.4 之后,为了提高 Java IO 的效率,Java 又提供了一套 New IO(NIO),原因在于它相对于之前的 IO 类库是新增的。此外,旧的 IO 类库提供的 IO 方法是阻塞的,New IO 类库则让 Java 可支持非阻塞 IO,所以,更多的人喜欢称之为非阻塞 IO(Non-blocking IO)
所有的系统 I/O 都分为两个阶段:等待就绪和操作。举例来说,读函数,分为等待系统可读和真正的读;同理,写函数分为等待网卡可以写和真正的写。Java IO 的各种流是阻塞的。这意味着当线程调用 write() 或 read() 时,线程会被阻塞,直到有一些数据可用于读取或数据被完全写入。
传统的阻塞型IO大致有如下几个缺点:
最典型的在实际生产环境中会遇到的,就是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 模型。
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个。
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;
}