NIO系列:2.NIO總結

寫在前面的話:

本文是對NIO的一些總結,對於需要從零基礎入門NIO的朋友,可以看下面這篇文章:
如何學習Java的NIO? - Java3y的回答 - 知乎
NIO主要有三個核心部分組成:
1)buffer緩衝區
2)Channel管道
3)Selector選擇器

個人學習感受就是,只要能理解NIO的三個核心部分,那麼就能基本掌握了NIO的使用以及體會NIO的整體思想。
下面開始對NIO的三個核心部分做一些總結。

------------------------------------------ 我是分割線 ------------------------------------------

緩衝區(Buffer)

緩衝區(Buffer):緩衝區本質上是一個可以讀寫數據的內存塊,可以理解成是一個容器對象(含數組),該對象提供了一組方法,可以更輕鬆地使用內存塊,緩衝區對象內置了一些機制,能夠跟蹤和記錄緩衝區的狀態變化情況。Channel 提供從文件、網絡讀取數據的渠道,但是讀取或寫入的數據都必須經由 Buffer
Buffer類及其子類:
在這裏插入圖片描述
常用Buffer子類一覽:
1)ByteBuffer,存儲字節數據到緩衝區
2)ShortBuffer,存儲字符串數據到緩衝區
3)CharBuffer,存儲字符數據到緩衝區
4)IntBuffer,存儲整數數據到緩衝區
5)LongBuffer,存儲長整型數據到緩衝區
6)DoubleBuffer,存儲小數到緩衝區
7)FloatBuffer,存儲小數到緩衝區

Buffer示例:

import java.nio.IntBuffer;

public class BasicBuffer {
    
    public static void main(String[] args) {

        //舉例說明Buffer 的使用 (簡單說明)
        //創建一個Buffer, 大小爲 5, 即可以存放5個int
        IntBuffer intBuffer = IntBuffer.allocate(5);

        //向buffer 存放數據
        intBuffer.put(10);
        intBuffer.put(11);
        intBuffer.put(12);
        intBuffer.put(13);
        intBuffer.put(14);
        for(int i = 0; i < intBuffer.capacity(); i++) {
            intBuffer.put( i * 2);
        }

        //如何從buffer讀取數據
        //將buffer轉換,讀寫切換
        intBuffer.flip();
        while (intBuffer.hasRemaining()) {
            System.out.println(intBuffer.get());
        }
    }
}

打印結果:

0
2
4
6
8
通道(Channel)

NIO的通道類似於流,但有些區別如下:
1)通道可以同時進行讀寫,而流只能讀或者只能寫
2)通道可以實現異步讀寫數據
3)通道可以從緩衝讀數據,也可以寫數據到緩衝:

Channel在NIO中是一個接口,常用的 Channel 類有:
FileChannel、DatagramChannel、ServerSocketChannel 和 SocketChannel
(ServerSocketChanne 類似 ServerSocket , SocketChannel 類似 Socket)
FileChannel用於文件的數據讀寫,DatagramChannel用於UDP的數據讀寫,ServerSocketChannel和SocketChannel用於TCP的數據讀寫。
在這裏插入圖片描述
FileChannel主要用來對本地文件進行 IO 操作,常見的方法有:
1)public int read(ByteBuffer dst) ,從通道讀取數據並放到緩衝區中
2)public int write(ByteBuffer src) ,把緩衝區的數據寫到通道中
3)public long transferFrom(ReadableByteChannel src, long position, long count),從目標通道中複製數據到當前通道
4)public long transferTo(long position, long count, WritableByteChannel target),把數據從當前通道複製給目標通道

MappedByteBuffer可以讓文件直接在內存(堆外的內存)中進行修改, 而如何同步到文件由NIO來完成
前面我們講的讀寫操作,都是通過一個Buffer 完成的,NIO 還支持通過多個Buffer (即 Buffer 數組) 完成讀寫操作,即 Scattering 和 Gathering

下面貼上一些Channel示例。

Channel示例1,使用前面學習後的 ByteBuffer(緩衝) 和 FileChannel(通道), 將 “hello,nio” 寫入到 file01.txt 中:

import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
 
public class NIOFileChannel01 {
    public static void main(String[] args) throws Exception{
 
        String str = "hello,nio";
        //創建一個輸出流->channel
        FileOutputStream fileOutputStream = new FileOutputStream("d:\\file01.txt");
 
        //通過 fileOutputStream 獲取 對應的 FileChannel
        //這個 fileChannel 真實 類型是  FileChannelImpl
        FileChannel fileChannel = fileOutputStream.getChannel();
 
        //創建一個緩衝區 ByteBuffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
 
        //將 str 放入 byteBuffer
        byteBuffer.put(str.getBytes());
 
 
        //對byteBuffer 進行flip
        byteBuffer.flip();
 
        //將byteBuffer 數據寫入到 fileChannel
        fileChannel.write(byteBuffer);
        fileOutputStream.close();
    }
}

Channel示例2,使用前面學習後的 ByteBuffer(緩衝) 和 FileChannel(通道), 將 file01.txt 中的數據讀入到程序,並顯示在控制檯屏幕:

import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
 
public class NIOFileChannel02 {
    public static void main(String[] args) throws Exception {
 
        //創建文件的輸入流
        File file = new File("d:\\file01.txt");
        FileInputStream fileInputStream = new FileInputStream(file);
 
        //通過fileInputStream 獲取對應的FileChannel -> 實際類型  FileChannelImpl
        FileChannel fileChannel = fileInputStream.getChannel();
 
        //創建緩衝區
        ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
 
        //將 通道的數據讀入到Buffer
        fileChannel.read(byteBuffer);
 
        //將byteBuffer 的 字節數據 轉成String
        System.out.println(new String(byteBuffer.array()));
        fileInputStream.close();
    }
}

Channel示例3,使用 FileChannel(通道) 和方法 read\write,完成文件的拷貝:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
 
public class NIOFileChannel03 {
    public static void main(String[] args) throws Exception {
 
        FileInputStream fileInputStream = new FileInputStream("1.txt");
        FileChannel fileChannel01 = fileInputStream.getChannel();
 
        FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
        FileChannel fileChannel02 = fileOutputStream.getChannel();
 
        ByteBuffer byteBuffer = ByteBuffer.allocate(512);
 
        while (true) { //循環讀取
 
            //這裏有一個重要的操作,一定不要忘了
            byteBuffer.clear(); //清空buffer
            int read = fileChannel01.read(byteBuffer);
            System.out.println("read =" + read);
            if(read == -1) { //表示讀完
                break;
            }
            //將buffer 中的數據寫入到 fileChannel02 -- 2.txt
            byteBuffer.flip();
            fileChannel02.write(byteBuffer);
        }
 
        //關閉相關的流
        fileInputStream.close();
        fileOutputStream.close();
    }
}
選擇器(Selector)

1)Java 的 NIO,用非阻塞的 IO 方式。可以用一個線程,處理多個的客戶端連接,就會使用到Selector(選擇器)
2)Selector 能夠檢測多個註冊的通道上是否有事件發生(注意:多個Channel以事件的方式可以註冊到同一個Selector),如果有事件發生,便獲取事件然後針對每個事件進行相應的處理。這樣就可以只用一個單線程去管理多個通道,也就是管理多個連接和請求。
3)只有在 連接/通道 真正有讀寫事件發生時,纔會進行讀寫,就大大地減少了系統開銷,並且不必爲每個連接都創建一個線程,不用去維護多個線程
4)避免了多線程之間的上下文切換導致的開銷

Selector類相關方法:

public abstract class Selector implements Closeable { 
    public static Selector open();              //得到一個選擇器對象
    public int select(long timeout);            //監控所有註冊的通道,當其中有 IO 操作可以進行時,將
                                                //對應的 SelectionKey 加入到內部集合中並返回,參數用來設置超時時間
    public Set<SelectionKey> selectedKeys();    //從內部集合中得到所有的 SelectionKey	
}

NIO中的 ServerSocketChannel功能類似BIO中的ServerSocket,SocketChannel功能類似Socket

SelectionKey相關方法:

public abstract class SelectionKey {
    public abstract Selector selector();               //得到與之關聯的 Selector 對象
    public abstract SelectableChannel channel();       //得到與之關聯的通道
    public final Object attachment();                  //得到與之關聯的共享數據
    public abstract SelectionKey interestOps(int ops); //設置或改變監聽事件
    public final boolean isAcceptable();               //是否可以accept
    public final boolean isReadable();                 //是否可以讀
    public final boolean isWritable();                 //是否可以寫
}

在這裏插入圖片描述
ServerSocketChannel 在服務器端監聽新的客戶端 Socket 連接,功能類似ServerSocket:

public abstract class ServerSocketChannel extends AbstractSelectableChannel implements NetworkChannel {
    public static ServerSocketChannel open()                         //得到一個 ServerSocketChannel 通道
    public final ServerSocketChannel bind(SocketAddress local)       //設置服務器端端口號
    public final SelectableChannel configureBlocking(boolean block)  //設置阻塞或非阻塞模式,取值 false 表示採用非阻塞模式
    public SocketChannel accept()                                    //接受一個連接,返回代表這個連接的通道對象
    public final SelectionKey register(Selector sel, int ops)        //註冊一個選擇器並設置監聽事件
}

在這裏插入圖片描述
SocketChannel,網絡 IO 通道,具體負責進行讀寫操作,功能類似Socket:

public abstract class SocketChannel extends AbstractSelectableChannel implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel{
    public static SocketChannel open();                             //得到一個 SocketChannel 通道
    public final SelectableChannel configureBlocking(boolean block);//設置阻塞或非阻塞模式,取值 false 表示採用非阻塞模式
    public boolean connect(SocketAddress remote);                   //連接服務器
    public boolean finishConnect();                                 //如果上面的方法連接失敗,接下來就要通過該方法完成連接操作
    public int write(ByteBuffer src);                               //往通道里寫數據
    public int read(ByteBuffer dst);                                //從通道里讀數據
    public final SelectionKey register(Selector sel, int ops, Object att);//註冊一個選擇器並設置監聽事件,最後一個參數可以設置共享數據
    public final void close();                                      //關閉通道
}

在這裏插入圖片描述

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