搜尋
首頁Javajava教程帶你完全掌握Java NIO(總結分享)

本篇文章為大家帶來了關於java的相關知識,其中主要介紹了NIO的相關問題,包括了NIO核心、BIO與NIO比較、透過NIO實現簡單的服務端客戶端通信,希望對大家有幫助。

帶你完全掌握Java NIO(總結分享)

推薦學習:《java教學

一、Java心智圖

帶你完全掌握Java NIO(總結分享)

#二、I/O模型

I/O模型的本質是用什麼樣的通道進行資料的傳送和接收,很大程度上決定了程式通訊的效能。
Java共支援三種網路程式設計模型:BIO、NIO、AIO

  • BIO:同步且阻塞,服務實作模式為一個連接一個線程,即客戶端有一個連接請求時,服務端就需要啟動一個執行緒進行處理。

  • NIO: 同步非阻塞,伺服器實作模式為一個執行緒處理多個請求連接,即客戶端發送的請求都會註冊到多工器上,多路復用器輪詢到連接有I/O請求就進行處理。

  • AIO:非同步非阻塞,AIO引入非同步通道的概念,採用了Proactor模式,簡化了程式編寫,有效的請求才啟動線程,它的特點是先由作業系統完成後才通知服務端。

三、BIO、NIO、AIO應用場景

  • #BIO方式適用於連接數目比較小且固定的架構,這種方式對伺服器資源需求比較高, 並發侷限於應用程式中,JDK1.4以前的唯一選擇,但程式簡單易理解。

  • NIO方式適用於連接數目多且連接比較短(輕操作)的架構,例如聊天伺服器,彈幕 系統,伺服器間通訊等。程式比較複雜,JDK1.4開始支援。

  • AIO方式使用於連接數目多且連接比較長(重操作)的架構,例如相簿伺服器,充分呼叫OS參與並發操作,程式設計比較複雜,JDK7開始支援

四、BIO程式設計簡單流程

  • 伺服器端啟動一個ServerSocket;

  • 客戶端啟動Socket對伺服器進行通信,預設伺服器端需要對每個客戶建立一個執行緒與之通訊;

  • 客戶端發出請求後, 先諮詢伺服器是否有執行緒回應,如果沒有則會等待,或被拒絕;

  • 如果有回應,客戶端執行緒會等待請求結束後,在繼續執行;

五、NIO核心

NIO 有三大核心部分:Selector(選擇器)、Channel(通道)、Buffer(緩衝區)。
NIO是面向緩衝區,或者說面向區塊編程,資料讀取到一個它稍後處理的緩衝區,需要時可在緩衝區中前後移動,這就增加了處理過程中的靈活性,使用它可以提供非阻塞式的高伸縮性網路。
HTTP2.0使用了多路復用的技術,做到同一個連線並發處理多個請求,而且並發請求 的數量比HTTP1.1大了好幾個數量級。
簡而言之,NIO可以一個執行緒處理多個請求。

六、BIO與NIO比較

  • BIO 以流的方式處理資料,而NIO 以區塊的方式處理資料,區塊I/O 的效率比流I /O 高很多;

  • BIO 是阻塞的,NIO 則是非阻塞的;

  • BIO基於位元組流和字元流進行操作,而NIO 是基於Channel(通道)和Buffer(緩衝區)進行操作,資料總是從通道讀取到緩衝區中,或從緩衝區寫入到通道中。 Selector(選擇器)用於監聽多個頻道的事件(例如:連線請求,資料到達等),因 此使用單一執行緒就可以監聽多個客戶端通道。

七、NIO 三大核心原理示意圖

帶你完全掌握Java NIO(總結分享)
# 流程圖說明:

  • Selector 對應一個線程, 一個線程對應多個channel(連接);

  • 該圖反應了有三個channel 註冊到該selector //程式;

  • 每個channel 都會對應一個Buffer;

  • #程式切換到哪個channel 是有事件決定的, Event 就是一個重要的概念;

  • Selector 會根據不同的事件,在各個通道上切換;

  • Buffer 就是一個記憶體區塊, 底層是有一個陣列;

  • 資料的讀取寫入是透過Buffer, 這個和BIO , BIO 中要么是輸入流,或者是輸出流, 不能雙向,但是NIO的Buffer 是可以讀也可以寫, 需要flip 方法切換;

  • channel 是雙向的, 可以返回底層作業系統的情況, 例如Linux , 底層的作業系統通道就是雙向的;

八、緩衝區(buffer)

緩衝區本質上是一個可以讀寫資料的記憶體區塊,可以理解成是一個容器物件(含數組),該物件提供了一組方法,可以更輕鬆地使用記憶體區塊,,緩衝區物件內建了一些機制,能夠追蹤和記錄緩衝區的狀態變化。 Channel 提供從檔案、 網路讀取資料的管道,但讀取或寫入的資料都必須經由 Buffer。
在 NIO 中,Buffer 是一個頂層父類,它是一個抽象類別。

1、常用Buffer子類別一覽

  • ByteBuffer,儲存位元組資料到緩衝區;

  • ShortBuffer,儲存字串資料到緩衝區;

  • CharBuffer,儲存字元資料到緩衝區;

  • ##IntBuffer,儲存整數資料到緩衝區;

  • LongBuffer,儲存長整數資料到緩衝區;

  • DoubleBuffer,儲存小數到緩衝區;

  • FloatBuffer,儲存小數到緩衝區;

#2、buffer四大屬性

  • mark:標記

  • position:位置,下一個要被讀或寫的元素的索引, 每次讀寫緩衝區資料時都會改變改值, 為下次讀寫作準備。

  • limit:表示緩衝區的目前終點,不能對緩衝區 超過極限的位置進行讀寫作業。且極限 是可以修改的

  • capacity:容量,即可以容納的最大資料量;在緩 衝區創建時被設定並且不能改變。

帶你完全掌握Java NIO(總結分享)

3、buffer常用api

JDK1.4時,引入的api

    public final int capacity( )//傳回此緩衝區的容量
  • public final int position( )//傳回此緩衝區的位置
  • public final Buffer position ( int newPositio)//設定此緩衝區的位置
  • public final int limit( )//傳回此緩衝區的限制
  • public final Buffer limit (int newLimit)//設定此緩衝區區的限制
  • public final Buffer mark( )//在此緩衝區的位置設定標記
  • public final Buffer reset( )//將此緩衝區的位置重設為先前標記的位置
  • public final Buffer clear( )//清除此緩衝區, 即將各個標記恢復到初始狀態,但是資料並沒有真正擦除, 後面操作會覆蓋
  • public final Buffer flip( )//反轉此緩衝區
  • public final Buffer rewind( )//重繞此緩衝區
  • public final int remaining( )//傳回目前位置與限制之間的元素數
  • public final boolean hasRemaining( )//告知在目前位置和限制之間是否有元素
  • public abstract boolean isReadOnly( );//告知此緩衝區是否為只告知此緩衝區是否為只讀取緩衝區
JDK1.6時引入的api

    #public abstract boolean hasArray();//告知此緩衝區是否具有可存取的底層實作陣列
  • public abstract Object array();//傳回此緩衝區的底層實作陣列
  • public abstract int arrayOffset();//傳回此緩衝區的底層實作陣列中第一個緩衝區元素的偏移
  • public abstract boolean isDirect();//告知此緩衝區是否為直接緩衝區

帶你完全掌握Java NIO(總結分享)




    。通道(channel)
  • 1、基本介紹
  • (1)NIO的通道類似串流

  • ##通道可以同時進行讀寫,而流只能讀或只能寫;
  • 通道可以實現異步讀寫數據

  • 通道可以從緩衝讀數據,也可以寫數據到緩衝
  • #(2)BIO 中的stream 是單向的,例如FileInputStream 物件只能進行讀取資料的操作,而NIO 中的通道(Channel)是雙向的,可以讀取操作,也可以寫入操作。 ### (3)Channel在NIO中是一個介面### (4)常用的 Channel 類別有:FileChannel、 DatagramChannel、ServerSocketChannel 和 SocketChannel。 ServerSocketChanne 類似 ServerSocket , SocketChannel 類似 Socket。 ### (5)FileChannel 用於檔案的資料讀寫, DatagramChannel 用於 UDP 的資料讀寫, ServerSocketChannel 和 SocketChannel 用於 TCP 的資料讀寫。 #########2、FileChannel#########FileChannel主要用來對本機檔案進行IO 操作,常見的方法有:############read ,從通道讀取資料並放到緩衝區中############write,把緩衝區的資料寫到通道中############transferFrom,從目標通道中複製資料到目前通道############transferTo,把資料從目前通道複製給目標通道###

3、關於Buffer 和Channel的注意事項和細節

  • #ByteBuffer 支援類型化的put 和get, put 放入的是什麼資料類型,get就應該使用對應的資料類型來取出,否則可能有BufferUnderflowException 異常。

  • 可以將一個普通Buffer 轉換成唯讀Buffer。

  • NIO 也提供了 MappedByteBuffer, 可以讓檔案直接在記憶體(堆外的記憶體)中進 行修改, 而如何同步到檔案由NIO 來完成。

  • NIO 也支援 透過多個 Buffer (即 Buffer 陣列) 完成讀寫操作,即 Scattering 和 Gathering。

十、Selector(選擇器)

1、基本介紹

  • Java 的NIO ,用非阻塞的IO 方式。可以用一個線程,處理多個的客戶端連 接,就會使用到Selector(選擇器)。

  • Selector 能夠偵測多個註冊的頻道上是否有事件發生,如果有事件發生,便取得事件然 後針對每個事件進行對應的處理。這樣就可以只用一個單線程去管理多個 通道,也就是管理多個連線和請求。

  • 只有在連接/通道真正有讀寫事件發生時,才會進行讀寫,就大大地減少了系統開銷,並且不必為每個連接都創建一個線程,不用去維護多個線程。

  • 避免了多執行緒之間的上下文切換所導致的開銷。

2、selector的相關方法

  • #open();//得到一個選擇器物件

  • select(long timeout);//監控所有註冊的通道,當其中有IO 操作可以進行時,將對應的SelectionKey 加入內部集合中並返回,參數用來設定逾時時間

  • selectedKeys();//從內部集合中得到所有的SelectionKey。

3、注意事項

NIO中的 ServerSocketChannel功能類似ServerSocket,SocketChannel功能類別 就像Socket。

十一、透過NIO實作簡單的服務端客戶端通訊

1、服務端

package com.nezha.guor.nio;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.*;import java.util.Iterator;public class NioServer {
    private Selector selector;
    private ServerSocketChannel serverSocketChannel;
    private static final int PORT = 8080;

    public NioServer() {
        try {
            //获得选择器
            selector = Selector.open();
            serverSocketChannel =  ServerSocketChannel.open();
            //绑定端口
            serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
            //设置非阻塞模式
            serverSocketChannel.configureBlocking(false);
            //将该ServerSocketChannel 注册到selector
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        }catch (IOException e) {
            System.out.println("NioServer error:"+e.getMessage());
        }
    }

    public void listen() {

        System.out.println("监听线程启动: " + Thread.currentThread().getName());
        try {
            while (true) {
                int count = selector.select();
                if(count > 0) {
                    //遍历得到selectionKey集合
                    Iterator<selectionkey> iterator = selector.selectedKeys().iterator();
                    while (iterator.hasNext()) {
                        SelectionKey key = iterator.next();

                        if(key.isAcceptable()) {
                            SocketChannel sc = serverSocketChannel.accept();
                            sc.configureBlocking(false);
                            sc.register(selector, SelectionKey.OP_READ);
                            System.out.println(sc.getRemoteAddress() + " 上线 ");
                        }
                        //通道发送read事件,即通道是可读的状态
                        if(key.isReadable()) {
                            getDataFromChannel(key);
                        }
                        //当前的key 删除,防止重复处理
                        iterator.remove();
                    }
                } else {
                    System.out.println("等待中");
                }
            }
        }catch (Exception e) {
            System.out.println("listen error:"+e.getMessage());
        }
    }

    private void getDataFromChannel(SelectionKey key) {
        SocketChannel channel = null;
        try {
            channel = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            int count = channel.read(buffer);
            //根据count的值做处理
            if(count > 0) {
                String msg = new String(buffer.array());
                System.out.println("来自客户端: " + msg);

                //向其它的客户端转发消息(排除自己)
                sendInfoToOtherClients(msg, channel);
            }
        }catch (IOException e) {
            try {
                System.out.println(channel.getRemoteAddress() + " 离线了");
                //取消注册
                key.cancel();
            }catch (IOException ex) {
                System.out.println("getDataFromChannel error:"+ex.getMessage());
            }
        }finally {
            try {
                channel.close();
            }catch (IOException ex) {
                System.out.println("channel.close() error:"+ex.getMessage());
            }
        }
    }

    //转发消息给其它客户(通道)
    private void sendInfoToOtherClients(String msg, SocketChannel self ) throws  IOException{
        System.out.println("服务器转发消息中...");
        System.out.println("服务器转发数据给客户端线程: " + Thread.currentThread().getName());
        //遍历 所有注册到selector 上的 SocketChannel,并排除 self
        for(SelectionKey key: selector.keys()) {
            Channel targetChannel = key.channel();

            //排除自己
            if(targetChannel instanceof  SocketChannel && targetChannel != self) {
                SocketChannel dest = (SocketChannel)targetChannel;
                //将信息存储到buffer
                ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
                //将buffer数据写入通道
                dest.write(buffer);
            }
        }
    }

    public static void main(String[] args) {
        //创建服务器对象
        NioServer nioServer = new NioServer();
        nioServer.listen();
    }}</selectionkey>

2、客戶端

package com.nezha.guor.nio;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.Scanner;public class NioClient {
    private final int PORT = 8080; //服务器端口
    private Selector selector;
    private SocketChannel socketChannel;
    private String username;

    public NioClient() throws IOException {
        selector = Selector.open();
        socketChannel = socketChannel.open(new InetSocketAddress("127.0.0.1", PORT));
        //设置非阻塞
        socketChannel.configureBlocking(false);
        //将channel注册到selector
        socketChannel.register(selector, SelectionKey.OP_READ);
        username = socketChannel.getLocalAddress().toString().substring(1);
        System.out.println(username + " is ok...");
    }

    //向服务器发送消息
    public void sendInfo(String info) {
        info = username + " 说:" + info;
        try {
            socketChannel.write(ByteBuffer.wrap(info.getBytes()));
        }catch (IOException e) {
            System.out.println("sendInfo error:"+e.getMessage());
        }
    }

    //读取从服务器端回复的消息
    public void readInfo() {
        try {
            int readChannels = selector.select();
            if(readChannels > 0) {
                Iterator<selectionkey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    if(key.isReadable()) {
                        //得到相关的通道
                        SocketChannel sc = (SocketChannel) key.channel();
                        //得到一个Buffer
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        //读取
                        sc.read(buffer);
                        //把读到的缓冲区的数据转成字符串
                        String msg = new String(buffer.array());
                        System.out.println(msg.trim());
                    }
                }
                iterator.remove(); //删除当前的selectionKey, 防止重复操作
            } else {
                System.out.println("没有可以用的通道...");
            }
        }catch (Exception e) {
            System.out.println("readInfo error:"+e.getMessage());
        }
    }

    public static void main(String[] args) throws Exception {
        NioClient nioClient = new NioClient();
        new Thread() {
            public void run() {
                while (true) {
                    nioClient.readInfo();
                    try {
                        Thread.currentThread().sleep(2000);
                    }catch (InterruptedException e) {
                        System.out.println("sleep error:"+e.getMessage());
                    }
                }
            }
        }.start();

        //发送数据给服务器端
        Scanner scanner = new Scanner(System.in);

        while (scanner.hasNextLine()) {
            nioClient.sendInfo(scanner.nextLine());
        }
    }}</selectionkey>

3、控制台輸出

帶你完全掌握Java NIO(總結分享)
帶你完全掌握Java NIO(總結分享)

#推薦學習:《java教學

以上是帶你完全掌握Java NIO(總結分享)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:CSDN。如有侵權,請聯絡admin@php.cn刪除
如何將Maven或Gradle用於高級Java項目管理,構建自動化和依賴性解決方案?如何將Maven或Gradle用於高級Java項目管理,構建自動化和依賴性解決方案?Mar 17, 2025 pm 05:46 PM

本文討論了使用Maven和Gradle進行Java項目管理,構建自動化和依賴性解決方案,以比較其方法和優化策略。

如何使用適當的版本控制和依賴項管理創建和使用自定義Java庫(JAR文件)?如何使用適當的版本控制和依賴項管理創建和使用自定義Java庫(JAR文件)?Mar 17, 2025 pm 05:45 PM

本文使用Maven和Gradle之類的工具討論了具有適當的版本控制和依賴關係管理的自定義Java庫(JAR文件)的創建和使用。

如何使用咖啡因或Guava Cache等庫在Java應用程序中實現多層緩存?如何使用咖啡因或Guava Cache等庫在Java應用程序中實現多層緩存?Mar 17, 2025 pm 05:44 PM

本文討論了使用咖啡因和Guava緩存在Java中實施多層緩存以提高應用程序性能。它涵蓋設置,集成和績效優勢,以及配置和驅逐政策管理最佳PRA

如何將JPA(Java持久性API)用於具有高級功能(例如緩存和懶惰加載)的對象相關映射?如何將JPA(Java持久性API)用於具有高級功能(例如緩存和懶惰加載)的對象相關映射?Mar 17, 2025 pm 05:43 PM

本文討論了使用JPA進行對象相關映射,並具有高級功能,例如緩存和懶惰加載。它涵蓋了設置,實體映射和優化性能的最佳實踐,同時突出潛在的陷阱。[159個字符]

Java的類負載機制如何起作用,包括不同的類載荷及其委託模型?Java的類負載機制如何起作用,包括不同的類載荷及其委託模型?Mar 17, 2025 pm 05:35 PM

Java的類上載涉及使用帶有引導,擴展程序和應用程序類負載器的分層系統加載,鏈接和初始化類。父代授權模型確保首先加載核心類別,從而影響自定義類LOA

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。