Java NIO(一)入门篇


概念

java.nio(java new IO),是jdk1.4 里提供的新api ,为所有的原始类型提供缓存支持。Sun 官方标榜的特性如下: 为所有的原始类型提供(Buffer)缓存支持。字符集编码解码解决方案。 Channel :一个新的原始I/O 抽象。 支持锁和内存映射文件的文件访问接口。 提供多路(non-bloking) 非阻塞式的高伸缩性网络I/O。

NIO和IO的主要差别   

      IO                      NIO

   面向流                 面向缓冲

   阻塞IO                阻塞IO

无                      selector

无                     channel

面向流与面向缓冲

Java NIOIO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。比如InputStream只能进行读取操作,而OutputStream只能进行写操作。Java NIO 提供 了channel,Channel和传统IO中的Stream很相似。虽然很相似,但是有很大的区别,主要区别为:通道是双向的,通过一个Channel既可以进行读,也可以进行写;它将数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据(如图)。




阻塞与非阻塞IO

Java IO的各种流是阻塞的。这意味着,当一个线程调用read()write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。


一个常见的网络 IO 通讯流程如下 :


从该网络通讯过程来理解一下何为阻塞 :

在以上过程中若连接还没到来,那么 accept 会阻塞 , 程序运行到这里不得不挂起, CPU 转而执行其他线程。

在以上过程中若数据还没准备好, read 会一样也会阻塞。

阻塞式网络 IO 的特点:多线程处理多个连接。每个线程拥有自己的栈空间并且占用一些 CPU 时间。每个线程遇到外部为准备好的时候,都会阻塞掉。阻塞的结果就是会带来大量的进程上下文切换。且大部分进程上下文切换可能是无意义的。比如假设一个线程监听一个端口,一天只会有几次请求进来,但是该 cpu 不得不为该线程不断做上下文切换尝试,大部分的切换以阻塞告终。

一个常见的网络NIO 通讯流程如下 :


把整个过程切换成小的任务,通过任务间协作完成。

由一个专门的线程来处理所有的 IO 事件,并负责分发。

事件驱动机制:事件到的时候触发,而不是同步的去监视事件。

线程通讯:线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的进程切换。

选择器(Selectors)

Selector类是NIO的核心类,Selector能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的响应处理。这样一来,只是用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,并且避免了多线程之间的上下文切换导致的开销。

与Selector有关的一个关键类是SelectionKey,一个SelectionKey表示一个到达的事件,这2个类构成了服务端处理业务的关键逻辑。

channel

在前面已经提到,Channel和传统IO中的Stream很相似。虽然很相似,但是有很大的区别,主要区别为:通道是双向的,通过一个Channel既可以进行读,也可以进行写;而Stream只能进行单向操作,通过一个Stream只能进行读或者写;

以下是常用的几种通道:

  • FileChannel
  • SocketChanel
  • ServerSocketChannel
  • DatagramChannel

  通过使用FileChannel可以从文件读或者向文件写入数据;通过SocketChannel,以TCP来向网络连接的两端读写数据;通过ServerSocketChanel能够监听客户端发起的TCP连接,并为每个TCP连接创建一个新的SocketChannel来进行数据读写;通过DatagramChannel,以UDP协议来向网络连接的两端读写数据。

下面给出通过FileChannel来向文件中写入数据的一个例子:

public  class  Test {
     public  static  void  main(String[] args)  throws  IOException  {
         File file =  new  File( "data.txt" );
         FileOutputStream outputStream =  new  FileOutputStream(file);
         FileChannel channel = outputStream.getChannel();
         ByteBuffer buffer = ByteBuffer.allocate( 1024 );
         String string =  "java nio" ;
         buffer.put(string.getBytes());
         buffer.flip();      //此处必须要调用buffer的flip方法
         channel.write(buffer);
         channel.close();
         outputStream.close();
     }  
}
通过上面的程序会向工程目录下的data.txt文件写入字符串"java nio", 注意在调用channel的write方法之前必须调用buffer的flip方法,否则无法正确写入内容。

你可能感兴趣的:(java基础)