Java NIO介绍及使用

1.什么是Java NIO

大家都知道Java BIO,其全称是java blocking IO,相对的Java NIO 全称为java non-blocking IO。顾名思义,java nio 是一种非阻塞IO。
下面是百度百科对Java NIO的介绍:
java.nio全称java non-blocking IO,是指jdk1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。

2 . NIO和BIO的区别

其本质就是阻塞与非阻塞的区别。

Java IO 的各种流都是阻塞的,这意味着,当一个线程进行流处理(如read()和write())时,无论是否有数据,该线程会一直被阻塞,直到流通信结束。在此期间线程不能干其他的事情,就算当前没有数据,线程依然保持等待状态。这样无疑会浪费大量的资源。而在NIO的非阻塞模式下,线程发送数据与接收数据都是通过“通道”进行的,线程只需要去询问通道是否有数据需要处理,有则处理,无则立即返回不会进行等待。线程通常将非阻塞IO的空闲时间用于处理其他通道上的IO事件,使用一个单独的线程就可以管理多个输入和输出通道。

那么NIO是怎么实现非阻塞的呢?其实原理很简单,NIO是面向块的,先把数据搬运过来,存放到一个缓冲区中,线程过一段时间来缓冲区看看,有没有数据,这个样线程就不需要始终关注IO了。

(BIO为同步阻塞模型,NIO为同步非阻塞模型。NIO没有实现异步,在JDK1.7后,升级了NIO库包,支持异步非阻塞通信模型,即AIO)

3 . Java NIO的几个相关概念

学习NIO,必须了解几个概念:
(1) . Buffer
Buffer是一个对象,它用来存放即将发送的数据和即将到来的数据。Buffer是NIO核心思想,它与普通流IO的区别是,普通流IO直接把数据写入或读取到Stream对象中,而NIO是先把读写数据交给Buffer,后在用流处理的。Buffer实际上就是一个数组,通常是字节数组,这个数组提供了访问数据的读写等操作属性,如位置,容量,上限等概念。
Buffer类型:

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer
    这写Buffer覆盖了你能通过IO发送的基本数据类型:byte , short , int , long , double , char 。

(2) .Channel
Channel(通道),与Stream(流)的不同之处在于通道是双向的,流只能在一个方向上操作(一个流必须是InputStream或者OutputStream的子类),而通道可以用于读,写或者二者同事进行,最关键的是可以和多路复用器结合起来,提供状态位,多路复用器可识别Channel所处的状态。

通道分两大类:用于网络读写的SelectableChannel,和用于文件操作的FileChannel。

(3) . Selector
NIO的编程基础,Selector提供选择已经就绪的任务的能力。简单说,就是Selector会不断轮询注册在Selector上的通道(Channel),如果这个通道发生了读写操作,这个通道就会处于就绪状态,会被Selector察觉到,然后通过SelectionKey可以取出就绪的Channel集合,从而进行IO操作。

一个Selector可以负责成千上万的通道,没有上限。这也是JDK使用了epoll代替传统的Select实现,获得连接句柄没有限制。意味着我们只需要一个线程负责Selector的轮询,就可以接入成百上千的客户端,这是JDK NIO库的巨大进步。

(4) . 原理图
图片来至于:
http://ifeve.com/overview/
这里写图片描述
这里写图片描述
这里写图片描述

这里写图片描述

4 . 使用例子

Server.class

public class Server implements Runnable
{
    private Selector selector;
    private ByteBuffer buffer = ByteBuffer.allocate(1024); //?

    public Server(int port){
        try {
            selector = Selector.open();
            ServerSocketChannel ssc = ServerSocketChannel.open();
            //设置服务器为非阻塞方式
            ssc.configureBlocking(false);
            ssc.bind(new InetSocketAddress(port));
            //把服务器通道注册到多路复用选择器上,并监听阻塞状态
            ssc.register(selector,SelectionKey.OP_ACCEPT);
            System.out.println("Server start whit port : "+port);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void run(){
        while(true){
            try {
                //会这这里处理事件,也是阻塞的,事件包括客户端连接,客户端发送数据到来,以及客户端断开连接等等
                //若没有事件发生,也会阻塞
                selector.select();
                //System.out.println("阻塞在这");
                //返回所有已经注册到多路复用选择器的通道的SelectionKey
                Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
                //遍历keys
                while(keys.hasNext()){
                    SelectionKey key = keys.next();
                    //下一个key,就像数组访问的i++
                    keys.remove();
                    if(key.isValid()){           //判断key是否有效
                        if(key.isAcceptable()){  //请求连接事件
                            accept(key);         //处理新客户的连接
                        }
                        if(key.isReadable()){    //有数据到来
                            read(key);
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 处理客户端连接
     * 服务器为每个客户端生成一个Channel
     * Channel与客户端对接
     * Channel绑定到Selector上
     * **/
    private void accept(SelectionKey key){
        try {
            //获取之前注册的SocketChannel通道
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            //执行阻塞方法,Channel和客户端对接
            SocketChannel sc = ssc.accept();
            //设置模式为非阻塞
            sc.configureBlocking(false);
            sc.register(selector,SelectionKey.OP_READ);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    private void read(SelectionKey key){

        try {
            //清空缓冲区的旧数据
            buffer.clear();
            SocketChannel sc = (SocketChannel) key.channel();
            int count = sc.read(buffer);
            if(count == -1){
                key.channel().close();
                key.cancel();
                return;
            }
            //读取到了数据,将buffer的position复位到0
            buffer.flip();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            String body = new String(bytes).trim();
            System.out.println("Server:"+body);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void main( String[] args )
    {
        new Thread(new Server(8379)).start();
    }
}

Client.class

public class Client {
    public static void main(String[] args){
        InetSocketAddress address = new InetSocketAddress("127.0.0.1",8379);
        SocketChannel sc =null;
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        try{
            sc = SocketChannel.open();
            sc.connect(address);
            while(true){
                byte[] bytes = new byte[1024];
                System.in.read(bytes);
                buffer.put(bytes);
                buffer.flip();
                sc.write(buffer);
                buffer.clear();
            }
        }catch(IOException e){
            e.printStackTrace();
        }finally {
            if(sc!=null){
                try {
                    sc.close();
                }catch (IOException e){
                    e.printStackTrace();
                }
            }
        }
    }
}

从服务器角度上看:
这里写图片描述
从客户端角度上看:
这里写图片描述

5 . 基于Java NIO的框架

推荐大家使用成熟的NIO框架,如Netty,MINA等。解决了很多NIO的陷阱,并屏蔽了操作系统的差异,有较好的性能和编程模型。

6.引用

https://blog.csdn.net/haoyuyang/article/details/53231585
http://ifeve.com/overview/

猜你喜欢

转载自blog.csdn.net/huxiny/article/details/80293378
今日推荐