天天看點

io模型合集及NIO詳解三大IO模型NIO三大元件NIO快速入門

三大IO模型

BIO模型

每一個請求開一個線程,缺點:

  • 這種方式勢必會造成線程開銷
  • 當請求沒有結果的時候,會造成阻塞
io模型合集及NIO詳解三大IO模型NIO三大元件NIO快速入門

NIO模型

一個線程處理多個請求,即用戶端發送的請求會發送到多路複用器上,多路複用器輪詢到連接配接又I/O請求就進行處理

io模型合集及NIO詳解三大IO模型NIO三大元件NIO快速入門

從上面這幅圖可以看出,一個線程維護一個選擇器,一個選擇器輪詢多個請求,隻要有請求帶着事件到來,就去處理----即netty這個架構是一個事件驅動的架構NIO架構

AIO模型

io模型合集及NIO詳解三大IO模型NIO三大元件NIO快速入門

NIO三大元件

NIO三大元件的關系:

三大元件:selector,channel,buffer

io模型合集及NIO詳解三大IO模型NIO三大元件NIO快速入門
  • 一個Select對應一個線程,一個線程對應多個channel
  • 程式切換到那個channel是由事件決定的
  • Selector會根據不同的事件,在各個通道上切換
  • Buffer是一個記憶體塊,底層是一個資料
  • 資料的讀取寫入是通過Buffer.在傳統Bio中要麼是輸入流,要麼是輸出流,不能是雙向,但是NIO的Buffer是可以讀也可以寫,需要通過flip方法進行切換
  • channel是雙向的
  • 在傳統的BIO模型中,如果一個請求沒有攜帶資料或者傳回資料,可能會造成線程阻塞,但是在NIO模型中,資料被緩沖在Buffer裡,用戶端隻能從Buffer中讀取資料及發送資料,如果緩沖區中沒有資料,伺服器的Select就不會區處理這個通道,而是處理其他Buffer區中有資料的通道

Nio與Bio的比較

io模型合集及NIO詳解三大IO模型NIO三大元件NIO快速入門

元件一 Buffer

簡單demo

public static void main(String[] args) {
        //建立一個Buffer,可以存放5個Int類型資料
        IntBuffer intBuffer=IntBuffer.allocate(5);
        for(int i=0;i<intBuffer.capacity();i++){
            //放資料
            intBuffer.put(i*2);
        }
        //将buffer轉換,讀寫切換(!!!!!)
        intBuffer.flip();
        while(intBuffer.hasRemaining()){
            System.out.println(intBuffer.get());
        }
    }
           

常用Buffer子類

io模型合集及NIO詳解三大IO模型NIO三大元件NIO快速入門

常用方法

buffer類的常用方法總結

元件二 通道channel

基本介紹

n類似于流,能夠同時進行讀寫操作

可以實作異步讀取資料

可以從緩沖區buffer讀取資料

io模型合集及NIO詳解三大IO模型NIO三大元件NIO快速入門

案例一:本地檔案寫資料

其實FileChannel是對輸出流的一個包裝

io模型合集及NIO詳解三大IO模型NIO三大元件NIO快速入門

代碼實列:’

public static void main(String[] args) throws IOException {
        String str="hello world";
        //建立一個輸出流
        FileOutputStream stream=new FileOutputStream("D://Aimg//test/5.txt");
        //這個fileChanel真是類型是FileChannelImpl
        FileChannel channel=stream.getChannel();
        //建立一個緩沖區
        ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
        //放str入byteBuffer
        byteBuffer.put(str.getBytes());
        //讀寫反裝
        byteBuffer.flip();
        //将緩沖區資料寫入通道
        channel.write(byteBuffer);
        stream.close();

    }
           
io模型合集及NIO詳解三大IO模型NIO三大元件NIO快速入門

案例二:本地檔案讀資料

public static void main(String[] args) throws IOException {
        //檔案
        File file=new File("D://Aimg//test/5.txt");
        FileInputStream inputStream=new FileInputStream(file);
        FileChannel channel=inputStream.getChannel();
        ByteBuffer byteBuffer=ByteBuffer.allocate((int)file.length());
        //将通道資料讀入到buffer中,注意,write方法是從緩沖區讀取資料到通道
         channel.read(byteBuffer);
        System.out.println(new String(byteBuffer.array()));
    }
           

上述兩個案列的示意圖:

io模型合集及NIO詳解三大IO模型NIO三大元件NIO快速入門

案例三:一個Buffer完成檔案的讀寫

流程:

io模型合集及NIO詳解三大IO模型NIO三大元件NIO快速入門
public static void main(String[] args) throws IOException {
        //檔案
        File file=new File("D://Aimg//test/5.txt");
        FileInputStream inputStream=new FileInputStream(file);
        FileChannel readChannel=inputStream.getChannel();
        FileOutputStream outputStream=new FileOutputStream("D://Aimg//test/6.txt");
        FileChannel writeChannel=outputStream.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while(true){
        //每次讀的時候要記得清空緩存
            buffer.clear();
            int read= readChannel.read(buffer);
            if(read!=-1){
            //讀寫反裝
                buffer.flip();
                writeChannel.write(buffer);
            }else break;
        }
    }
           

案例四:channel的transferFrom拷貝檔案

public static void main(String[] args) throws IOException {
        //檔案
        File file=new File("D://Aimg//12.jpg");
        FileInputStream inputStream=new FileInputStream(file);
        FileChannel readChannel=inputStream.getChannel();
        FileOutputStream outputStream=new FileOutputStream("D://Aimg//13.jpg");
        FileChannel writeChannel=outputStream.getChannel();
        writeChannel.transferFrom(readChannel,0,readChannel.size());
    }
           

元件三 Selector選擇器

常用方法

io模型合集及NIO詳解三大IO模型NIO三大元件NIO快速入門

一個channel通道對應了一個selectKey,每次有事件發生時,selector會根據擷取的selectKeys數組來周遊channel

Selector關系圖及解析:

io模型合集及NIO詳解三大IO模型NIO三大元件NIO快速入門

說明:

  • 當用戶端連接配接時,會通過ServerSocketChannel

    ,得到SocketChannel

  • 将socketChannel注冊到Selector上,并且傳回一個

    selectorKey,該SelectorKey會和Selector關聯

  • selector進行監聽select方法,傳回有事件發生的通道個數
  • 進一步得到各個SelectKey
  • 再通過SelectKey,反向擷取channel
  • 最後通過channel完成對應的事件

NIO快速入門

在學習nio入門前,請先學習前一篇文章,理清nio三大元件的關系

圖解關系:

io模型合集及NIO詳解三大IO模型NIO三大元件NIO快速入門

說明:

  • 當用戶端連接配接時,會通過ServerSocketChannel

    ,得到SocketChannel

  • 将socketChannel注冊到Selector上,并且傳回一個

    selectorKey,該SelectorKey會和Selector關聯

  • selector進行監聽select方法,傳回有事件發生的通道個數
  • 進一步得到各個SelectKey
  • 再通過SelectKey,反向擷取channel
  • 最後通過channel完成對應的事件

服務端:

public static void main(String[] args) throws IOException {
        //建立serverSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //建立selector對象
        Selector selector = Selector.open();
        //綁定端口6666,在伺服器端監聽
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        //設定非阻塞
        serverSocketChannel.configureBlocking(false);
        //把ServerSocketChannel注冊到Selector
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        //循環等待用戶端請求
        while (true) {
            if (selector.select(1000) == 0) {
                System.out.println("伺服器等待了1秒,無連接配接");
                continue;
            }
            //擷取selectorKey集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            //周遊集合
            for (SelectionKey key : selectionKeys) {
                //反向擷取到channel,根據不同僚件做出處理
                if (key.isAcceptable()) {//如果是連接配接請求
                    //給該用戶端生成一個socketChannel
                    SocketChannel channel = serverSocketChannel.accept();
                    //将目前的channel注冊到selector上
                    channel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                if (key.isReadable()) {//讀的請求
                    //擷取到該channel
                    SocketChannel channel = (SocketChannel) key.channel();
                    //擷取buffer
                    ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
                    channel.read(byteBuffer);
                    System.out.println("from 用戶端" + new String(byteBuffer.array()));

                }
                //最後移出
                selectionKeys.remove(key);
            }
        }
    }
           

用戶端:

public static void main(String[] args) throws IOException {
        SocketChannel socketChannel=SocketChannel.open();
        //非阻塞
        socketChannel.configureBlocking(false);
        //提供服務端的ip和端口
        InetSocketAddress address=new InetSocketAddress("127.0.0.1",6666);
        //連接配接伺服器
        if(!socketChannel.connect(address)){
            while(!socketChannel.finishConnect()){
                System.out.println("因為連接配接需要事件,可以做其他的事情");
            }
        }else {
            //連接配接成功了
            String str="hello world";
            ByteBuffer byteBuffer=ByteBuffer.wrap(str.getBytes());
            //将資料寫入channel
            socketChannel.write(byteBuffer);

        }
    }
           

繼續閱讀