IO與NIO的主要區別是什麼

① IO是面向流的,NIO是面向緩衝區的
② IO是阻塞的,NIO是非阻塞的
③ IO無Selector,NIO需要Selector

  • NIO即New IO,這個庫是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但實現方式不同,NIO主要用到的是塊,所以NIO的效率要比IO高很多。在JavaAPI中提供了兩套NIO,一套是針對標準輸入輸出NIO,另一套就是網絡編程NIO。

  • 1、面向流與面向緩衝

    Java IO和NIO之間第一個最大的區別是,IO是面向流的,NIO是面向緩衝區的。 Java IO面向流意味着每次從流中讀一個或多個字節,直至讀取所有字節,它們沒有被緩存在任何地方。此外,它不能前後移動流中的數據。如果需要前後移動從流中讀取的數據,需要先將它緩存到一個緩衝區。 Java NIO的緩衝導向方法略有不同。數據讀取到一個它稍後處理的緩衝區,需要時可在緩衝區中前後移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩衝區中包含所有您需要處理的數據。而且,需確保當更多的數據讀入緩衝區時,不要覆蓋緩衝區裏尚未處理的數據。

  • 2、阻塞與非阻塞IO

    Java IO的各種流是阻塞的。這意味着,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據完全寫入。該線程在此期間不能再幹任何事情了。Java NIO的非阻塞模式,使一個線程從某通道發送請求讀取數據,但是它僅能得到目前可用的數據,如果目前沒有數據可用時,就什麼都不會獲取,而不是保持線程阻塞,所以直至數據變的可以讀取之前,該線程可以繼續做其他的事情。 非阻塞寫也是如此。一個線程請求寫入一些數據到某通道,但不需要等待它完全寫入,這個線程同時可以去做別的事情。 線程通常將非阻塞IO的空閒時間用於在其它通道上執行IO操作,所以一個單獨的線程現在可以管理多個輸入和輸出通道(channel)。

  • 3、選擇器(Selectors)
    Java NIO的選擇器允許一個單獨的線程來監視多個輸入通道,你可以註冊多個通道使用一個選擇器,然後使用一個單獨的線程來“選擇”通道:這些通道里已經有可以處理的輸入,或者選擇已準備寫入的通道。這種選擇機制,使得一個單獨的線程很容易來管理多個通道。

1、API調用

當然,使用NIO的API調用時看起來與使用IO時有所不同,但這並不意外,因爲並不是僅從一個InputStream逐字節讀取,而是數據必須先讀入緩衝區再處理

IO讀取數據代碼示例:

private static void readLine() {
        String path = "/Volumes/code/java/test/springdemo/src/main/resources/banner.txt";
        InputStream inputStream = null;
        try {
            inputStream = new BufferedInputStream(new FileInputStream(path));
            InputStreamReader reader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(reader);
            String line;
            while ( (line = bufferedReader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

請注意處理狀態由程序執行多久決定。換句話說,一旦reader.readLine()方法返回,你就知道肯定文本行就已讀完, readline()阻塞直到整行讀完,這就是原因。
在這裏插入圖片描述

而NIO的實現有所不同:

 private static void nioRead() {
        String path = "/Volumes/code/java/test/springdemo/src/main/resources/test.txt";
        RandomAccessFile accessFile = null;

        try {
            accessFile = new RandomAccessFile(path, "rw");
            // 獲取channel
            FileChannel channel = accessFile.getChannel();
            // 創建buffer並且分配空間大小爲1024
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            // 從管道中讀取數據寫入到Buffer中
            int bytesRead = channel.read(byteBuffer);

            while (bytesRead != -1) {
                // 將position設爲0,limit 指向下一個不可以操作的元素爲止
                byteBuffer.flip();
                // 如果緩衝區還有內容
                while (byteBuffer.hasRemaining()) {
                    System.out.println((char)byteBuffer.get());
                }
                // 將buffer 中魏都區的數據拷貝到Buffer的起始位置,供下次讀取
                byteBuffer.compact();

                bytesRead = channel.read(byteBuffer);
            }

        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            try {
                if (accessFile != null) {
                    accessFile.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

注意channel.read()這一行,從通道讀取字節到ByteBuffer。當這個方法調用返回時,你不知道你所需的所有數據是否在緩衝區內。你所知道的是,該緩衝區包含一些字節,這使得處理有點困難。假設第一次 read(buffer)調用後,讀入緩衝區的數據只有半行,你能處理數據嗎?顯然不能,需要等待,直到整行數據讀入緩存,在此之前,對數據的任何處理毫無意義。所以,你怎麼知道是否該緩衝區包含足夠的數據可以處理呢?好了,你不知道。發現的方法只能查看緩衝區中的數據。其結果是,在你知道所有數據都在緩衝區裏之前,你必須檢查幾次緩衝區的數據。這不僅效率低下,而且可以使程序設計方案雜亂不堪。

總結:

  1. NIO可讓您只使用一個(或幾個)單線程管理多個通道(網絡連接或文件),但付出的代價是解析數據可能會比從一個阻塞流中讀取數據更復雜。
  2. 如果需要管理同時打開的成千上萬個連接,這些連接每次只是發送少量的數據,例如聊天服務器,實現NIO的服務器可能是一個優勢。同樣,如果你需要維持許多打開的連接到其他計算機上,如P2P網絡中,使用一個單獨的線程來管理你所有出站連接,可能是一個優勢。一個線程多個連接的設計方案如下圖所示:

在這裏插入圖片描述

  • SelectionKey.OP_ACCEPT —— 接收連接繼續事件,表示服務器監聽到了客戶連接,服務器可以接收這個連接了
  • SelectionKey.OP_CONNECT —— 連接就緒事件,表示客戶與服務器的連接已經建立成功
  • SelectionKey.OP_READ —— 讀就緒事件,表示通道中已經有了可讀的數據,可以執行讀操作了(通道目前有數據,可以進行讀操作了)
  • SelectionKey.OP_WRITE —— 寫就緒事件,表示已經可以向通道寫數據了(通道目前可以用於寫操作)
/**
 * fshows.com
 * Copyright (C) 2013-2020 All Rights Reserved.
 */
package com.example.springdemo.test.io;

/**
 * @author xuleyan
 * @version NIOServer.java, v 0.1 2020-04-06 11:03 AM xuleyan
 */
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NIOServer {

    /*標識數字*/
    private  int flag = 0;
    /*緩衝區大小*/
    private  int BLOCK = 4096;
    /*接受數據緩衝區*/
    private  ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
    /*發送數據緩衝區*/
    private  ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);
    private  Selector selector;

    public NIOServer(int port) throws IOException {
        // 打開服務器套接字通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 服務器配置爲非阻塞
        serverSocketChannel.configureBlocking(false);
        // 檢索與此通道關聯的服務器套接字
        ServerSocket serverSocket = serverSocketChannel.socket();
        // 進行服務的綁定
        serverSocket.bind(new InetSocketAddress(port));
        // 通過open()方法找到Selector
        selector = Selector.open();
        // 註冊到selector,等待連接
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("Server Start----8888:");
    }


    // 監聽
    private void listen() throws IOException, InterruptedException {
        while (true) {
            // 選擇一組鍵,並且相應的通道已經打開
            selector.select();
            // 返回此選擇器的已選擇鍵集。
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                iterator.remove();
                handleKey(selectionKey);
            }
        }
    }

    // 處理請求
    private void handleKey(SelectionKey selectionKey) throws IOException, InterruptedException {
        // 接受請求
        ServerSocketChannel server = null;
        SocketChannel client = null;
        String receiveText;
        String sendText;
        int count=0;
        // 測試此鍵的通道是否已準備好接受新的套接字連接。
        if (selectionKey.isAcceptable()) {
            // 返回爲之創建此鍵的通道。
            server = (ServerSocketChannel) selectionKey.channel();
            // 接受到此通道套接字的連接。
            // 此方法返回的套接字通道(如果有)將處於阻塞模式。
            client = server.accept();
            // 配置爲非阻塞
            client.configureBlocking(false);
            // 註冊到selector,等待連接
            client.register(selector, SelectionKey.OP_READ);
        } else if (selectionKey.isReadable()) {
            // 返回爲之創建此鍵的通道。
            client = (SocketChannel) selectionKey.channel();
            //將緩衝區清空以備下次讀取
            receivebuffer.clear();
            //讀取服務器發送來的數據到緩衝區中
            count = client.read(receivebuffer);
            if (count > 0) {
                receiveText = new String( receivebuffer.array(),0,count);
                System.out.println("服務器端接受客戶端數據--:"+receiveText);
                Thread.sleep(2000);
                client.register(selector, SelectionKey.OP_WRITE);
            }
        } else if (selectionKey.isWritable()) {
            //將緩衝區清空以備下次寫入
            sendbuffer.clear();
            // 返回爲之創建此鍵的通道。
            client = (SocketChannel) selectionKey.channel();
            sendText="message from server--" + flag++;
            //向緩衝區中輸入數據
            sendbuffer.put(sendText.getBytes());
            //將緩衝區各標誌復位,因爲向裏面put了數據標誌被改變要想從中讀取數據發向服務器,就要復位
            sendbuffer.flip();
            //輸出到通道
            client.write(sendbuffer);
            System.out.println("服務器端向客戶端發送數據--:"+sendText);
            Thread.sleep(2000);
            client.register(selector, SelectionKey.OP_READ);
        }
    }

    /**
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException, InterruptedException {
        // TODO Auto-generated method stub
        int port = 8888;
        NIOServer server = new NIOServer(port);
        server.listen();
    }
}
/**
 * fshows.com
 * Copyright (C) 2013-2020 All Rights Reserved.
 */
package com.example.springdemo.test.io;

/**
 * @author xuleyan
 * @version ClientServer.java, v 0.1 2020-04-06 11:04 AM xuleyan
 */
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NIOClient {

    /*標識數字*/
    private static int flag = 0;
    /*緩衝區大小*/
    private static int BLOCK = 4096;
    /*接受數據緩衝區*/
    private static ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
    /*發送數據緩衝區*/
    private static ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);
    /*服務器端地址*/
    private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress(
            "localhost", 8888);

    public static void main(String[] args) throws IOException, InterruptedException {
        // TODO Auto-generated method stub
        // 打開socket通道
        SocketChannel socketChannel = SocketChannel.open();
        // 設置爲非阻塞方式
        socketChannel.configureBlocking(false);
        // 打開選擇器
        Selector selector = Selector.open();
        // 註冊連接服務端socket動作
        socketChannel.register(selector, SelectionKey.OP_CONNECT);
        // 連接
        socketChannel.connect(SERVER_ADDRESS);
        // 分配緩衝區大小內存

        Set<SelectionKey> selectionKeys;
        Iterator<SelectionKey> iterator;
        SelectionKey selectionKey;
        SocketChannel client;
        String receiveText;
        String sendText;
        int count=0;

        while (true) {
            //選擇一組鍵,其相應的通道已爲 I/O 操作準備就緒。
            //此方法執行處於阻塞模式的選擇操作。
            selector.select();
            //返回此選擇器的已選擇鍵集。
            selectionKeys = selector.selectedKeys();
            //System.out.println(selectionKeys.size());
            iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                selectionKey = iterator.next();
                if (selectionKey.isConnectable()) {
                    System.out.println("client connect");
                    client = (SocketChannel) selectionKey.channel();
                    // 判斷此通道上是否正在進行連接操作。
                    // 完成套接字通道的連接過程。
                    if (client.isConnectionPending()) {
                        client.finishConnect();
                        System.out.println("完成連接!");
                        Thread.sleep(2000);
                        sendbuffer.clear();
                        sendbuffer.put("Hello,Server".getBytes());
                        sendbuffer.flip();
                        client.write(sendbuffer);
                    }
                    client.register(selector, SelectionKey.OP_READ);
                } else if (selectionKey.isReadable()) {
                    client = (SocketChannel) selectionKey.channel();
                    //將緩衝區清空以備下次讀取
                    receivebuffer.clear();
                    //讀取服務器發送來的數據到緩衝區中
                    count=client.read(receivebuffer);
                    if(count>0){
                        receiveText = new String( receivebuffer.array(),0,count);
                        System.out.println("客戶端接受服務器端數據--:"+receiveText);
                        Thread.sleep(2000);
                        client.register(selector, SelectionKey.OP_WRITE);
                    }

                } else if (selectionKey.isWritable()) {
                    sendbuffer.clear();
                    client = (SocketChannel) selectionKey.channel();
                    sendText = "message from client--" + (flag++);
                    sendbuffer.put(sendText.getBytes());
                    //將緩衝區各標誌復位,因爲向裏面put了數據標誌被改變要想從中讀取數據發向服務器,就要復位
                    sendbuffer.flip();
                    client.write(sendbuffer);
                    System.out.println("客戶端向服務器端發送數據--:"+sendText);
                    Thread.sleep(2000);
                    client.register(selector, SelectionKey.OP_READ);
                }
            }
            selectionKeys.clear();
        }
    }
}

參考:https://www.cnblogs.com/xiaoxi/p/6576588.html
參考:https://blog.csdn.net/qq_24365213/article/details/77159713

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章