NIO学习二、NIO的基本使用

这是作为学习NIO的总结,如有不对,请大佬指出。

一、基本操作(这些操作不会的时候查文档就行)

     从一个buffer中读写到另一个buffer
    @Test
    public void bufferTest1(){
        try {
            RandomAccessFile readAccessFile=new RandomAccessFile("C:\\Users\\e550c\\Desktop\\日结.txt","rw");
            RandomAccessFile writeAccessFile=new RandomAccessFile("C:\\Users\\e550c\\Desktop\\temp.txt","rw");

            FileChannel readChannel=readAccessFile.getChannel();
            FileChannel writeChannel=writeAccessFile.getChannel();
            //将一个channel数据送入另一个channel(这样做可以实现文件的写入)
            //与之相对的是transferFrom
            readChannel.transferTo(0,readAccessFile.length(), writeChannel);

            readAccessFile.close();
            writeAccessFile.close();;

        } catch (FileNotFoundException e) {


        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 其他常见的:
  Buffer.rewind()将position设回0,所以你可以重读Buffer中的所有数据。             
  mark()与reset()方法 通过调用Buffer.mark()方法,可以标记Buffer中的
  一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。

二、Scatter与Gather

        scatter:是将channel中的数据写入到多个buffer中,先写满第一个,然后写入下一个:
        Gater:将多个buffer数据先聚集在一起,然后写入到指定的buffer中。
       try {
            ByteBuffer byteBuffer1=ByteBuffer.allocate(10);
            ByteBuffer byteBuffer2=ByteBuffer.allocate(10);
            RandomAccessFile randomAccessFile=new RandomAccessFile("C:\\Users\\e550c\\Desktop\\temp.txt","rw");
            byteBuffer1.clear();
            byteBuffer2.clear();
            byteBuffer1.put("hello".getBytes());
            byteBuffer2.put("OK,world".getBytes());
            byteBuffer1.flip();
            byteBuffer2.flip();
            FileChannel fileChannel=randomAccessFile.getChannel();
            //将多个byteBuffer聚集在一起写入
            fileChannel.write(new ByteBuffer[]{byteBuffer1,byteBuffer2});

            byteBuffer1.clear();
            byteBuffer2.clear();
            fileChannel.position(0L);
            //先写满第一个,第一个满后再写入第二个
            fileChannel.read(new ByteBuffer[]{byteBuffer1,byteBuffer2});
            byteBuffer1.flip();
            while(byteBuffer1.hasRemaining()){
                System.out.print((char)byteBuffer1.get());
            }
            System.out.println();
            byteBuffer2.flip();
            while(byteBuffer2.hasRemaining()){
                System.out.print((char)byteBuffer2.get());
            }
            fileChannel.close();
            randomAccessFile.close();
        }catch (Exception e){
            e.printStackTrace();;
        }

三、非阻塞IO操作

    所有的非阻塞IO的channel都实现了SelectableChannel接口,而FileChannle没有实现这个接口,所以它不支持非阻塞IO
     以一个ServerSocketChannel与SocketChannel为例子来演示:不懂的话可以先看看java网络编程。
      服务端代码:
        try {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

            serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1", 1234));//监听1234端口
            serverSocketChannel.configureBlocking(false);//这样配置后就是同步非阻塞的

            SocketChannel socketChannel = null;
            while ((socketChannel = serverSocketChannel.accept()) == null) {//如果没有客户端连接的话立即返回
                System.out.println("try to Linking:");               //不会阻塞
                TimeUnit.SECONDS.sleep(1);
            }
            ByteBuffer byteBuffer1 = ByteBuffer.allocate(10);
            ByteBuffer byteBuffer2 = ByteBuffer.allocate(10);
            socketChannel.read(new ByteBuffer[]{byteBuffer1, byteBuffer2});

            byteBuffer1.flip();//回到初始位置
            byteBuffer2.flip();
            while (byteBuffer1.hasRemaining()) {
                System.out.println((char) byteBuffer1.get());
            }
            System.out.println("-----------------");
            while (byteBuffer2.hasRemaining()) {
                System.out.println((char) byteBuffer2.get());
            }
            socketChannel.close();//读取完之后手动关闭这个连接
            serverSocketChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
客户端代码
          SocketChannel socketChannel=null;
        try {
            socketChannel=SocketChannel.open();//打开socket
            socketChannel.connect(new InetSocketAddress("127.0.0.1",1234));//连接上指定服务端口
            ByteBuffer byteBuffer1=ByteBuffer.allocate(12);
            ByteBuffer byteBuffer2=ByteBuffer.allocate(12);
            byteBuffer1.put("hello,world".getBytes());

            byteBuffer1.flip();

            byteBuffer2.put("hello,next".getBytes());
            byteBuffer2.flip();

            socketChannel.write(byteBuffer1);
            socketChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

四、Piple管道操作:

       Java NIO管道是两个线程之间单项的数据连接。Piple有一个source通道和一个sink通道。数据会被写到sink通道,从source通道中读取。
    /*
    * Piple进行管道
    * */
    @Test
    public void test16(){
        try {
            //创建一个piple通道
            Pipe pipe=Pipe.open();
            //创建一个写通道
            WritableByteChannel writableByteChannel=pipe.sink();
            //c创建一个读通道,从读通道中获取数据
            ReadableByteChannel readableByteChannel=pipe.source();


            //创建一个线程从sink写入数据
            WorkerThread workerThread=new WorkerThread(writableByteChannel);
            workerThread.start();
            ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
            while(readableByteChannel.read(byteBuffer)>=0){
                byteBuffer.flip();
                byte[] bytes=new byte[byteBuffer.remaining()];

                byteBuffer.get(bytes);
                String str=new String(bytes);
                System.out.println(str);
                byteBuffer.clear();
            }
            readableByteChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }



    private static class WorkerThread  extends Thread{
        WritableByteChannel writableByteChannel;
        public WorkerThread(WritableByteChannel writableByteChannel){
            this.writableByteChannel=writableByteChannel;
        }

        @Override
        public void run() {
            ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
            for (int i=0;i<10;i++){
                String str="piple sink data"+i;
                byteBuffer.put(str.getBytes());
                byteBuffer.flip();

                try {
                    writableByteChannel.write(byteBuffer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                byteBuffer.clear();
            }
            try{
                writableByteChannel.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

五、Selector操作(最重要的,压轴的&&&)

 channle与selector适配使用的,创建了非阻塞的channel之后,注册到selector中,返
   回注册的SelectionKey,并设置感兴趣的事件,也就是说当这些事件发生的时候,对应的
   Channel就准备就绪,可以使用。如果没有发生,那么就等待。
 ps:有时候感觉channel出现很突兀,是因为selector 直接管理 Java Socket 很难实现
   ,所以使用channel做一次封装与之适配。这一切都是为了selector而存在的。
如下所示:
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
感兴趣的事件是通过SelectionKey来获得:有如下四种:
      SelectionKey.OP_ACCEPT —— 接收连接继续事件,表示服务器监听到了客户连接,
      服务器可以接收这个连接了
      SelectionKey.OP_CONNECT —— 连接就绪事件,表示客户与服务器的连接已经建立成功
      SelectionKey.OP_READ--读就绪事件,表示可以读了(通道目前有数据,可以进行读操作了)
      SelectionKey.OP_WRITE —— 写就绪事件,表示已经可以向通道写数据了(通道目前可
      以用于写操作)
      操作了(通道目前有数据,可以进行读操作了)
selector中两个重要的函数:          
      selector.select():返回当前就绪的channel数量,如果没有就绪channel,那么它就
      阻塞,直到就绪channel将它唤醒。            
            其他的select():
            select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)。 
            selectNow()不会阻塞,不管什么通道就绪都立刻返回(此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,
            则此方法直接返回零)。
     selector.SelectionKey():返回已经准备就绪的channel注册标记的set集合,单个
     SelectionKey通过channel()为此对应的SelectionKey创建通道.其他操作查文档就好了。

呼呼~~~~接下来是一个例子:仍然以ServerSocketChannel与SocketChannel为例子:
//服务端:
          try {
            ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
            serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1",1234));
            //设置非阻塞状态
            serverSocketChannel.configureBlocking(false);
            //创建一个Selector
            Selector selector=Selector.open();
            //将serverSocketChannel注册到selecor中,它的返回值为对应的SelectionKey
            //它对ACCPET事件感兴趣,也就是说当客户端连接的时候,它准备就绪,可以被Selector调用
            serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
            //分配空间
            ByteBuffer byteBuffer=ByteBuffer.allocate(128);
            while(true){
                //如果没有就绪事件,它就阻塞
                int n=selector.select();
                //获取就绪的channel对应的注册标记集合
                Set<SelectionKey> set=selector.selectedKeys();
                Iterator<SelectionKey> iterator=set.iterator();
                while(iterator.hasNext()){
                    SelectionKey selectionKey=iterator.next();
                    //注意当得到一个SelectionKey之后必须移除它,不然会陷入死循环
                    iterator.remove();
                    if (selectionKey.isAcceptable()){
                        //触发accpet之后,它会将连接的SocketChannel注册到register中
                        //它感兴趣的事件是读事件,当通道中有数据之后,它准备就绪
                        //SocketChannel必须是同步非阻塞的
                        SocketChannel socketChannel=serverSocketChannel.accept();
                        socketChannel.configureBlocking(false);
                        socketChannel.register(selector,SelectionKey.OP_READ);
                    }else if (selectionKey.isReadable()){
                        SocketChannel socketChannel= (SocketChannel) selectionKey.channel();

                        byteBuffer.clear();
                        socketChannel.read(byteBuffer);
                        byteBuffer.flip();
                        while(byteBuffer.hasRemaining()){
                            System.out.print((char)byteBuffer.get());

                        }
                        System.out.println();
                        socketChannel.close();
                    }

                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }  
//客户端
            SocketChannel socketChannel = null;

            try {
                socketChannel = SocketChannel.open();

                socketChannel.connect(new InetSocketAddress("127.0.0.1", 1234));
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

                byteBuffer.put("hello,world".getBytes());
                byteBuffer.flip();
                socketChannel.write(byteBuffer);
                socketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

另外:针对UDP的数据传输,文件锁等等等,不是全部能写完的,多看文档,多练~~

posted @ 2017-12-03 22:45  十禾。  阅读(229)  评论(0编辑  收藏  举报