• Java NIO2:NIO概述


    一、概述

         从JDK1.4开始,Java提供了一系列改进的输入/输出处理的新特性,被统称为NIO(即New I/O)。新增了许多用于处理输入输出的类,这些类都被放在java.nio包及子包下,并且对原java.io包中的很多类进行改写,新增了满足NIO的功能。NIO采用内存映射文件的方式来处理输入输出,NIO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样访问文件了。

    NIO 与原来的 I/O 有同样的作用和目的,但是它使用不同的方式? I/O。块 I/O 的效率可以比流 I/O 高许多。

    流与块的比较

         原来的 I/O 库(在 java.io.*中) 与 NIO 最重要的区别是数据打包和传输的方式。正如前面提到的,原来的 I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。

         面向流 的 I/O 系统一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。为流式数据创建过滤器非常容易。链接几个过滤器,以便每个过滤器只负责单个复杂处理机制的一部分,这样也是相对简单的。不利的一面是,面向流的 I/O 通常相当慢。

         一个 面向块 的 I/O 系统以块的形式处理数据。每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。

         在NIO中有几个核心对象需要掌握:缓冲区(Buffer)、通道(Channel)、选择器(Selector)。

    二、缓冲区Buffer

         缓冲区实际上是一个容器对象,更直接的说,其实就是一个数组,在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,它也是写入到缓冲区中的;任何时候访问 NIO 中的数据,都是将它放到缓冲区中。而在面向流I/O系统中,所有数据都是直接写入或者直接将数据读取到Stream对象中。具体看下面这张图就理解了:

        上面的图描述了从一个客户端向服务端发送数据,然后服务端接收数据的过程。客户端发送数据时,必须先将数据存入Buffer中,然后将Buffer中的内容写入通道。服务端这边接收数据必须通过Channel将数据读入到Buffer中,然后再从Buffer中取出数据来处理。

        在NIO中,所有的缓冲区类型都继承于抽象类Buffer,最常用的就是ByteBuffer,对于Java中的基本类型,基本都有一个具体Buffer类型与之相对应,它们之间的继承关系如下图所示:

    下面是一个简单使用IntBuffer的例子:

    package com.demo.nio;
    
    import java.nio.IntBuffer;
    
    public class TestIntBuffer {
    
        public static void main(String[] args) {
            // 分配新的int缓冲区,参数为缓冲区容量
            // 新缓冲区的当前位置将为零,其界限(限制位置)将为其容量。它将具有一个底层实现数组,其数组偏移量将为零。
            IntBuffer buffer = IntBuffer.allocate(8);
    
            for (int i = 0; i < buffer.capacity(); ++i) {
                int j = 2 * (i + 1);
                // 将给定整数写入此缓冲区的当前位置,当前位置递增
                buffer.put(j);
            }
    
            // 重设此缓冲区,将限制设置为当前位置,然后将当前位置设置为0
            buffer.flip();
    
            // 查看在当前位置和限制位置之间是否有元素
            while (buffer.hasRemaining()) {
                // 读取此缓冲区当前位置的整数,然后当前位置递增
                int j = buffer.get();
                System.out.print(j + "  ");
            }
        }
    }

    运行后可以看到:

    三、通道Channel

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

          通道是一个对象,通过它可以读取和写入数据,当然了所有数据都通过Buffer对象来处理。我们永远不会将字节直接写入通道中,相反是将数据写入包含一个或者多个字节的缓冲区。同样不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。

          在NIO中,提供了多种通道对象,而所有的通道对象都实现了Channel接口。它们之间的继承关系如下图所示:

        Channel(通道)表示到实体如硬件设备、文件、网络套接字或可以执行一个或多个不同I/O操作的程序组件的开放的连接。所有的Channel都不是通过构造器创建的,而是通过传统的节点InputStream、OutputStream的getChannel方法来返回响应的Channel。

      Channel中最常用的三个类方法就是map、read和write,其中map方法用于将Channel对应的部分或全部数据映射成ByteBuffer,而read或write方法有一系列的重载形式,这些方法用于从Buffer中读取数据或向Buffer中写入数据。

    1、使用NIO读取数据

    在前面我们说过,任何时候读取数据,都不是直接从通道读取,而是从通道读取到缓冲区。所以使用NIO读取数据可以分为下面三个步骤: 
    (1). 从FileInputStream获取Channel 
    (2). 创建Buffer 
    (3). 将数据从Channel读取到Buffer中

    下面是一个简单的使用NIO从文件中读取数据的例子:

    import java.io.*;  
    import java.nio.*;  
    import java.nio.channels.*;  
      
    public class Program {  
        public static void main( String args[] ) throws Exception {  
            FileInputStream fin = new FileInputStream("c:\test.txt");  
              
            // 获取通道  
            FileChannel fc = fin.getChannel();  
              
            // 创建缓冲区  
            ByteBuffer buffer = ByteBuffer.allocate(1024);  
              
            // 读取数据到缓冲区  
            fc.read(buffer);  
              
            buffer.flip();  
              
            while (buffer.remaining()>0) {  
                byte b = buffer.get();  
                System.out.print(((char)b));  
            }  
              
            fin.close();  
        }  
    }  

    2、使用NIO写入数据

    使用NIO写入数据与读取数据的过程类似,同样数据不是直接写入通道,而是写入缓冲区,可以分为下面三个步骤:
    (1). 从FileInputStream获取Channel
    (2). 创建Buffer
    (3). 将数据从Channel写入到Buffer中

    下面是一个简单的使用NIO向文件中写入数据的例子:

    package com.demo.nio;
    
    import java.io.FileOutputStream;
    import java.nio.ByteBuffer;
    import java.nio.channels.FileChannel;
    
    public class Program {
        
        private static final byte message[] = { 83, 111, 109, 101, 32,
            98, 121, 116, 101, 115, 46 };
    
        public static void main( String args[] ) throws Exception {
            FileOutputStream fout = new FileOutputStream( "c:\test.txt" );
            
            FileChannel fc = fout.getChannel();
            
            ByteBuffer buffer = ByteBuffer.allocate( 1024 );
            
            for (int i=0; i<message.length; ++i) {
                buffer.put( message[i] );
            }
            
            buffer.flip();
            
            fc.write( buffer );
            
            fout.close();
        }
    
    }

    四、选择器Selector

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

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

  • 相关阅读:
    全文检索(SOLR)前端应用浅析续 Rails前端分析
    全文搜索应用 企业搜索SearchBlox
    持续集成(CI) 几种测试的区别(摘录)
    图解全文检索SOLR的酷应用[Ajax Solr]
    Php如何实现下载功能超详细流程分析
    在MySQL字段中使用逗号分隔符
    session的垃圾回收机制
    【转】apache常用配置
    深入理解PHP之数组(遍历顺序)
    正则表达式的子模式详解
  • 原文地址:https://www.cnblogs.com/xiaoxi/p/6534926.html
Copyright © 2020-2023  润新知