NIO學習二、NIO的基本使用

大多多發表於2017-12-03

這是作為學習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的資料傳輸,檔案鎖等等等,不是全部能寫完的,多看文件,多練~~

相關文章