javaNIO 學習筆記(四)

javaNIO 學習筆記(四)

Java NIO Selector

selector(選擇器)作爲 Java NIO的一個組件,它可以檢查一個或多個Java NIO通道實例,並確定哪些通道可以讀取或寫入。通過這種方式,一個線程可以管理多個通道,從而實現多個網絡連接。因爲是單線程處理多個通道的信息,因此用於處理通道數據的線程數就會減少,這樣就可以節省下線程之間切換的時間成本。

開啓selector

selector.open()

註冊selector

channel.register()

將監聽器register到非阻塞channel,並指定監聽的事件,並得到一個SelectionKey實例。

通道必須處於非阻塞模式才能與選擇器一起使用。這意味着你不能使用FileChannel的選擇器,因爲FileChannel的不能切換到非阻塞模式。

監聽一般有四個事件
這四個事件由四個SelectionKey常量表示:
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT:就是就緒
SelectionKey.OP_READ
SelectionKey.OP_WRITE
如果想監聽多個事件可以按照如下方式寫:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

SelectionKey

一些常見方法

//返回SelectionKey的attachment,attachment可以在註冊channel的時候指定。
SelectionKey.attachment(object);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

// 返回該SelectionKey對應的channel。
SelectionKey.channel(); 
// 返回該SelectionKey對應的Selector。
SelectionKey.selector(); 
//返回代表需要Selector監控的IO操作的bit mask
SelectionKey.interestOps(); 
// 返回一個bit mask,代表在相應channel上可以進行的IO操作。
SelectionKey.readyOps(); 

// 創建ready集合的方法
int readySet = selectionKey.readyOps();
//檢查這些操作是否就緒的方法
SelectionKey.isAcceptable();//是否可讀,是返回 true
boolean isWritable()//是否可寫,是返回 true
boolean isConnectable()//是否可連接,是返回 true
boolean isAcceptable()//是否可接收,是返回 true

// ---  判斷Selector是否對Channel的某種事件感興趣 ---
int interestSet = selectionKey.interestOps(); 
boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;    

Channel channel = key.channel();
Selector selector = key.selector();

select()方法:返回值表示有多少通道已經就緒,一旦調用select()方法,並且返回值不爲0時,則 可以通過調用Selector的selectedKeys()方法來訪問已選擇鍵集合 。

  • int select():阻塞到至少有一個通道在你註冊的事件上就緒了。
  • int select(long timeout):和select()一樣,但最長阻塞時間爲timeout毫秒。
  • int selectNow():非阻塞,只要有通道就緒就立刻返回。

選擇器執行選擇的過程,系統底層會依次詢問每個通道是否已經就緒,這個過程可能會造成調用線程進入阻塞狀態,那麼我們有以下三種方式可以喚醒在select()方法中阻塞的線程。

  • wakeup()方法 :通過調用Selector對象的wakeup()方法讓處在阻塞狀態的select()方法立刻返回 該方法使得選擇器上的第一個還沒有返回的選擇操作立即返回。如果當前沒有進行中的選擇操作,那麼下一次對select()方法的一次調用將立即返回。

  • close()方法 :通過close()方法關閉Selector該方法使得任何一個在選擇操作中阻塞的線程都被喚醒(類似wakeup()),同時使得註冊到該Selector的所有Channel被註銷,所有的鍵將被取消,但是Channel本身並不會關閉。

下面的代碼是參考文檔中對應給出的。

package jniolearn.selector;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * @Author: jimmy
 * @Date: 2020/6/15 22:15
 * @Description:
 */
public class SelectorPrc {

    public static void main(String[] args) throws IOException {

        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.socket().bind(new InetSocketAddress("localhost", 8080));
        // 設置爲非則色
        ssc.configureBlocking(false);
        // 開啓一個選擇器
        Selector selector = Selector.open();
        // 註冊選擇器和監聽的事件
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        while(true) {
            int readyNum = selector.select();
            if (readyNum == 0) {
                continue;
            }

            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> it = selectedKeys.iterator();

            while(it.hasNext()) {
                SelectionKey key = it.next();

                if(key.isAcceptable()) {
                    // 接受連接
                } else if (key.isReadable()) {
                    // 通道可讀
                } else if (key.isWritable()) {
                    // 通道可寫
                }
				// keyIterator.remove()調用。選擇器不會從所選鍵集本身移除SelectionKey實例。處理完通道時,必須這樣做。下次通道變爲“ready”時,選擇器將再次將其添加到選中的鍵集。
                it.remove();
            }
        }

    }
}

Java NIO Pipe

pipe的意思是管道,那麼前面幾篇學習中我說channel也可以理解爲管道是錯誤的,這裏更正下,channel應該理解爲通道,防止概念混亂。

管道單項連接2給線程的。管道具有源通道和接收通道。將數據寫入接收通道。然後可以從源通道讀取該數據。

package jniolearn.channel;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;

/**
 * @Author: jimmy
 * @Date: 2020/6/15 23:44
 * @Description:
 */
public class NioPipe {

    public static void main(String[] args) throws IOException {

        // 開啓pipe
        Pipe pipe = Pipe.open();
        // 接受通道開啓
        Pipe.SinkChannel sinkChannel = pipe.sink();
        // 準備數據
        String newData = "jimmy pipe" ;

        ByteBuffer buf = ByteBuffer.allocate(48);
        buf.clear();
        buf.put(newData.getBytes());
        buf.flip();
        // 寫入數據
        while(buf.hasRemaining()) {
            sinkChannel.write(buf);
        }

        // 接受數據
        ByteBuffer buffer = ByteBuffer.allocate(48);
        Pipe.SourceChannel sourceChannel = pipe.source();
        int len = sourceChannel.read(buffer);
        buffer.flip();
        System.out.println(new String(buffer.array(), 0, len));

        // 4. 關閉管道
        sinkChannel.close();
        sourceChannel.close();
    }
}

Java NIO Path

JDK7新增Path接口,Paths工具類,Files工具類。 這些接口和工具類對NIO中的功能進行了高度封裝,大大簡化了文件系統的IO編程。

分別位於java.nio.file、java.nio.channels包下

Path

Path接口表示的是一個與平臺無關的路徑(文件和目錄都用Path表示)。

Path類中包含了常用的操作路徑的方法,

  • getRoot()  -Path對象的跟路徑

  • toAbsolutePath() -Path對象的絕對路徑

  • getNameCount() -Path對象裏包含的路徑數量

Path path = Paths.get("c:\\data\\myfile.txt");

在Unix系統(Linux, MacOS, FreeBSD等)上,上面的絕對路徑看起來像這樣:

Path = Paths.get("/home/jakobjenkov/myfile.txt");

Path接口的normalize()方法可以規範化路徑

String originalPath =
        "d:\\data\\projects\\a-project\\..\\another-project";

Path path1 = Paths.get(originalPath);
System.out.println("path1 = " + path1);

Path path2 = path1.normalize();
System.out.println("path2 = " + path2);

// 規範化路徑不包含a-project\..部分,因爲這是多餘的。被刪除的部分沒有添加到最終的絕對路徑。
path1 = d:\data\projects\a-project\..\another-project
path2 = d:\data\projects\another-project
    
    
p1.toFile();    

Paths工具類

Paths工具類包含了兩個返回Path對象的靜態方法。

  • static Path get(URI uri) 直接通過路徑返回Path對象
  • static Path get(String first, String…more)通過給出的String字符串組裝成一個Path對象
package jniolearn;

import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * @Author: jimmy
 * @Date: 2020/6/16 00:10
 * @Description:
 */
public class PathPrc {
    public static void main(String[] args) {
        //在傳統java.io中, 文件和目錄都被抽象成File對象, 即 File file = new File(".");
        //NIO.2中則引入接口Path代表與平臺無關的路徑,文件和目錄都用Path對象表示
        //通過路徑工具類Paths返回一個路徑對象Path
        Path path = Paths.get(".");
        System.out.println("path裏包含的路徑數量:" + path.getNameCount());
        System.out.println("path的根路徑: "+path.getRoot());
        //path的絕對路徑
        //對比傳統java.io, 取絕對路徑 file.getAbsoluteFile()
        Path absolutePath = path.toAbsolutePath();
        System.out.println("path的絕對路徑:");
        System.out.println(absolutePath);
        System.out.println("absolutePath的根路徑: "+absolutePath.getRoot());
        System.out.println("absolutePath裏包含的路徑數量:" + absolutePath.getNameCount());
        System.out.println(absolutePath.getName(3));
        //以多個String來構建path -》 java.lang.IllegalArgumentException,路徑不存在會報錯
        Path path2 = Paths.get("D:\", "myProject" , "ThreadNotes");
        System.out.println(path2);
    }
}

下一篇學習file和異步Channel。

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