NIO


----NIO简介----

      为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络
      
      IO操作的模式:
    PIO(Programing IO):所有的IO操作由CPU处理,CPU占用率比较高
    
    DMA(Direct Memory Access):CPU把IO操作控制权交给DMA控制器,只能以固定的方式读写,CPU空闲做其他工作
    
    通道方式(Channel):能执行有限通道指令的IO控制器,代替CPU管理控制外设。通道有自己的指令系统,是一个协处理器,具有更强的独立处理数据输入和输出的能力

Java NIO由以下几个核心部分组成:
    Buffer:缓冲区
    Channel:通道
    Selector:选择器(轮询器)


----Buffer的使用----

      Java NIO中的Buffer用于和NIO通道进行交互,数据是从通道读入缓冲区,从缓冲区写入到通道中的

      缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存,这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该快内存

Java NIO里关键的Buffer实现:
    ByteBuffer    CharBuffer    DoubleBuffer    FloatBuffer
    IntBuffer      LongBuffer    ShortBuffer

1、Buffer的基本用法:
      使用Buffer读写数据一般遵循以下四个步骤:
    1)创建缓冲区,写入数据到Buffer
    2)调flip()方法
    3)从Buffer中读取数据
    4)调用clear()方法或者comparct()方法

      1.创建缓冲区,容量1024
    ByteBuffer buffer = ByteBuffer.allocate(1024);
      2.写入数据
            buffer.put("abcde".getBytes());
      3.把写入模式转换成读取模式
         buffer.flip();
      4.读取(limit,buffer里有多少可读数据值就是多少)
         byte[] data = new byte[buffer.limit()];
            buffer.get(data);
        System.out.println(new String(data));

2、Buffer

      capacity:容量
    作为一个内存块,Buffer有一个固定大小值,只能往里写capacity个byte、long,char等类型,一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据

      position:位置
    当你写数据到Buffer中时,position表示当前的位置。初始的position值为0。当数据写入到Buffer后,position会向前移动到下一个可插入数据的Buffer单元。positoin最大可为capacity
    当读取数据时,也是从某个特定位置读,当将Buffer从写模式切换到读模式,position会被重置为0,当从Buffer的position出读取数据时,position向前移动到下一个可读的位置

      limit:限制
    在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据,写模式下,limit等于capacity
    当切换到读模式时,limit表示你最多能读到多少数据

3、常用方法:
    
      put();    //放入数据

?     get();    //获取数据

?    

      Buffer的分配:
    想要获得一个Buffer对象首先要进行分配,每一个Buffer类都有一个allocate方法
        ByteBuffer buf = ByteBuffer.allocate(1024);    //创建简接缓冲区,大小为1024字节
        ByteBuffer buf2 = ByteBuffer.allocateDirect(1024);    //直接缓冲区

    直接缓冲区和简接缓冲区的区别:
        间接缓冲区:在堆中开辟,垃圾回收器可以回收,空间不大,读写文件速度较慢
        直接缓冲区:在物理内存中开辟空间,空间较大,读写文件速度快,不受垃圾回收器控制,创建和销毁耗性能
      flip()方法
    将Buffer从写模式切换到读模式,调用flip方法会将position设回0,并将limit设置成之前position的值
    position现在用于标记读的位置,limit表示之前写进了多少个数据        
      
      rewind()方法:返回
    将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变,仍表示能从Buffer中读取多少个元素

      clear() 与 compact() 方法
    clear();   //清空

?    compact();   //清空,会保留未读取的数据

?    
    如果调用的是clear方法,position将设回0,limit将被设置为capacity的值,(清空),但是数据还在
    如果Buffer中有未读完的数据,调用clear方法,数据将被遗忘
    调用compact方法,所有未读数据拷贝到Buffer起始处,然后将position设到最后一个未读元素正后面

      mark 与 reset 方法
    mark();   //做标记

?    reset();   //返回上一个标记

?    

----Channel----(通道)

      所有的IO在NIO中都从一个Channel开始,Channel有点像流,数据可以从Channel读到Buffer中,也可以从Buffer写到Channel

FileChannel的基本使用

      FileCHannel是一个连接到文件的通道,可以通过文件通道读写文件

      FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下

1、创建FileChannel

      第一种:使用一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel对象
    RandomAccessFile raf = new RandomAccessFile("a.txt","rw");
    FileChannel fc = raf.getChannel();

      第二种:jdk1.7后才能使用,FileChannel.open 方法
    FileChannel fc = FileChannel.open(Paths.get("a.txt"),StandardOpenOption.READ);
    
2、从FIleChannel读取数据

        首先,分配一个Buffer,从FileChannel中读取的数据将被读到Buffer中,然后调用FileChannel.read 方法,read返回int值表示有多少字节被读到了Buffer中,如果返回-1 表示到了文件尾

        ByteBuffer buf = ByteBuffer.allocate(1024);
        while (channel.read(buf)>0){
            //切换为读取模式
            buf.flip();
            String data = new String(buf.array(),0,buf.limit());
            System.out.println(data);
            buf.clear()
        }

3、向FileChannel写数据

        使用FileChannel.write()方法向FileChannel写数据    

        ByteBuffer buf = ByteBuffer.allocate(1024);
        buf.put("好好学习".getBytes());
        channel.write(buf);


        使用内存映射文件复制大文件:

        FileChannel readChannel = FileChannel.open(Paths.get("aaa.avi"), StandardOpenOption.READ);
        FileChannel writeChannel = FileChannel.open(Paths.get("bbb.avi"), StandardOpenOption.WRITE);
                                
        MappedByteBuffer map = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, readChannel.size());
        writeChannel.write(map);


----Selector和非阻塞网络编程----

传统的网络编程
    TCP   ServerSocket Socket( accept  阻塞式)
    UDP  DatagramSocket  DatagramPacket  ( receive )

1、ServerSocketChannel、SocketChannel实现阻塞式网络编程

    ServerSocketChannel是一个基于通道的Socket监听器(服务器套接字),等同于ServerSocket类。
    SocketChannel是一个基于通道的客户端套接字,等同于Socket类


        1)、使用NIO实现TCP的服务器端(阻塞式)

    //1创建ServerSocketChannel
            ServerSocketChannel listener = ServerSocketChannel.open();
            //2绑定端口号 SocketAddress 套接字地址(InetAddress+端口号)、InetAddress(ip地址,不包含端口号)
            listener.bind(new InetSocketAddress("10.9.21.242",1234));
            //3监听
            SocketChannel socketChannel = listener.accept();
            //4读取数据
           ByteBuffer buf = ByteBuffer.allocate(1024*4);
            while (socketChannel.read(buf)>0){
                    buf.flip();
                    String data = new String(buf.array(),0,buf.limit());
                    System.out.println(socketChannel.getRemoteAddress()+"说:"+data);
                    buf.clear();
            }
            //5关闭
            socketChannel.close();
            listener.close();


         2)、使用NIO实现TCP的客户端(阻塞式)

    //1创建客户端套接字通道
            SocketChannel sc = SocketChannel.open(new InetSocketAddress("10.9.21.224",1234));
            //2写入
            ByteBuffer buf = ByteBuffer.allocate(1024*4);
            buf.put("好久不见".getBytes());
            buf.flip();
            sc.write(buf);
            //3关闭
            sc.close();
    
2、Selector简介
    
         要使用Selector,得向Selector注册Channel,然后调用它的Select()方法,这个方法会一直阻塞到某个注册的通道有事建就绪,一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。

?    
         Selector 允许单线程处理多个Channel。仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道。事实上,可以只用一个线程处理所有的通道,这样会大量的减少线程之间上下文切换的开销。

         选择器(Selector):Selector选择器类管理者一个被注册的通道集合的信息和它们的就绪状态,通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态

         可选择通道(SelectableSchannel):这个抽象类提供了实现通道的可选择性所需要的公共方法。它是所有支持就绪检查的通道类的父类。因为FileChannel类没有继承SelectableChannel,因此它不是可选通道,而所有socket通道都是可选择的,SocketChannel和ServerSocketChannel是SelectableChannel的子类

         选择键(SelectionKey):选择键封装了特定的通道与特定的选择器的注册关系,选择键对象被SelectableChannel.register()返回并提供一个表示这种注册关系的标记。选择键包含了两个比特集(以整数的形式进行编码),选择键支持四种操作类型:
    Connect  连接
    Accept  接收请求
    Read  读
    Write  写
Java中定义了四个常量来表示这四种操作类型:
    SelectionKey.OP_CONNECT
    SelectionKey.OP_ACCEPT
    SelectionKey.OP_READ
    SelectionKey.OP_WRITE


         1)使用一个线程处理多个客户端请求(使用非阻塞式网络编程)(服务器端)
    //1创建ServerSocketChannel
     ServerSocketChannel listener=ServerSocketChannel.open();
            //2绑定地址    
    listener.bind(new InetSocketAddress("10.9.21.146", 9999));
            //3设置为非阻塞式
            listener.configureBlocking(false);
            //4创建Selector(轮询器)
            Selector selector=Selector.open();
            //5注册轮询器
            listener.register(selector, SelectionKey.OP_ACCEPT);
            //6轮询处理    
            while(selector.select()>0){    //select();阻塞方法,当轮询器有事件发生则返回个数
                    //7获取所有的事件
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> it = selectionKeys.iterator();
                    while(it.hasNext()){
                        SelectionKey selectionKey = it.next();
                        //判断事件类型
                        if(selectionKey.isAcceptable()){ //新的客户连接
                            //8处理请求
                            SocketChannel socketChannel = listener.accept();
                            //9设置非阻塞模式
                            socketChannel.configureBlocking(false);
                            //10注册轮询器
                            socketChannel.register(selector, SelectionKey.OP_READ);
                        }else if(selectionKey.isReadable()){// 新的数据发送过来
                                //接受数据
                                //11获取发生读取事件的SocketChannel
                                SocketChannel sc = (SocketChannel) selectionKey.channel();
                                //12创建ByteBuffer
                                ByteBuffer buf=ByteBuffer.allocate(1024*4);
                                int len=-1;
                                //read()不会阻塞,没有数据返回0,有数据返回数据个数 ,如果客户端关闭或结束返回-1
                                while((len=sc.read(buf))>0){
                                       buf.flip();//切换读取模式
                                        String data=new String(buf.array(),0,buf.limit());
                                        InetSocketAddress isa = (InetSocketAddress) sc.getRemoteAddress();
                                        System.out.println(isa.getAddress()+"说:"+data);
                                       buf.clear();
                                }
                                if(len==-1){
                                        sc.close();//关闭SocketChannel
                                }
                        }
                        //把处理过的事件删除
                        it.remove();
                    }
    }

         2)客户端(使用非阻塞式网络编程)
    //1创建SocketChannel
            SocketChannel sc=SocketChannel.open(new InetSocketAddress("10.9.21.146", 9999));
            //2设置为非阻塞
            sc.configureBlocking(false);
           //3写入数据
            Scanner input=new Scanner(System.in);
            ByteBuffer buffer=ByteBuffer.allocate(1024*4);
            while(true){
                    String d=input.next();
                    buffer.put(d.getBytes());
                    buffer.flip();
                    sc.write(buffer);
                    buffer.clear();
                   if(d.equals("baibai")){
                       break;
                    }
            }
            //4关闭
            sc.close();


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值