中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

杭州做網(wǎng)站外包公司哪家好優(yōu)化seo網(wǎng)站

杭州做網(wǎng)站外包公司哪家好,優(yōu)化seo網(wǎng)站,新開河街做網(wǎng)站公司,b站看大片畫質(zhì)清晰一、Selector 1.1 Selector簡(jiǎn)介 1.1.1 Selector 和 Channel的關(guān)系 Selector 一般稱為選擇器 ,也可以翻譯為 多路復(fù)用器 。 它是 Java NIO 核心組件中的一個(gè),用于檢查一個(gè)或多個(gè) NIO Channel(通道)的狀態(tài)是否處于可讀、可寫。由…

一、Selector

1.1 Selector簡(jiǎn)介

1.1.1 Selector 和 Channel的關(guān)系

Selector 一般稱為選擇器 ,也可以翻譯為 多路復(fù)用器 。

它是 Java NIO 核心組件中的一個(gè),用于檢查一個(gè)或多個(gè) NIO Channel(通道)的狀態(tài)是否處于可讀、可寫。由此可以實(shí)現(xiàn)單線程管理多個(gè) channels,也就是可以管理多個(gè)網(wǎng)絡(luò)鏈接。

image-20240224150132333

使用 Selector 的好處: 使用更少的線程來就可以來處理通道了, 相比使用多個(gè)線程,避免了線程上下文切換帶來的開銷。

1.1.2 可選擇通道

  • 不是所有的 Channel 都可以被 Selector 復(fù)用的。

    • 判斷 Channel 能被 Selector 復(fù)用的前提:是否繼承了抽象類 SelectableChannel

    • 如果繼承了 SelectableChannel,則可以被復(fù)用,否則不能

  • SelectableChannel 類提供了實(shí)現(xiàn)通道的可選擇性所需要的公共方法。它是所有支持就緒檢查的通道類的父類

    • 所有 socket 通道,都繼承了 SelectableChannel 類,都是可選擇的,包括從管道(Pipe)對(duì)象的中獲得的通道

    • FileChannel 類,沒有繼承 SelectableChannel,因此是不可選擇通道,不能被選擇器復(fù)用

  • 一個(gè)通道可以被注冊(cè)到多個(gè)選擇器上,但對(duì)每個(gè)選擇器而言只能被注冊(cè)一次

    • 通道和選擇器之間的關(guān)系,使用注冊(cè)的方式完成
    • SelectableChannel 可以被注冊(cè)到 Selector 對(duì)象上,在注冊(cè)的時(shí)候,需要指定通道的哪些操作是 Selector 感興趣的

image-20240302192354504

1.1.3 Channel 注冊(cè)到 Selector

  • 使用 Channel.register(Selector sel,int ops) 方法,將一個(gè)通道注冊(cè)到一個(gè)選擇器

    • Selector sel 指定通道要注冊(cè)的選擇器
    • int ops 指定選擇器需要查詢的通道操作??梢怨┻x擇器查詢的通道操作,從類型來分,包括以下四種
      • 可讀 : SelectionKey.OP_READ
      • 可寫 : SelectionKey.OP_WRITE
      • 連接 : SelectionKey.OP_CONNECT
      • 接收 : SelectionKey.OP_ACCEPT
      • 如果 Selector 對(duì)通道的多操作類型感興趣,可以用“位或”操作符來實(shí)現(xiàn):比如:int key = SelectionKey.OP_READ | SelectionKey.OP_WRITE ;
  • 選擇器查詢的不是通道的操作,而是通道的某個(gè)操作的一種就緒狀態(tài)

    什么是操作的就緒狀態(tài)?

    一旦通道具備完成某個(gè)操作的條件,表示該通道的某個(gè)操作已經(jīng)就緒,就可以被 Selector 查詢到,程序可以對(duì)通道進(jìn)行對(duì)應(yīng)的操作

    比如:

    • 某個(gè)SocketChannel 通道可以連接到一個(gè)服務(wù)器,則處于“連接就緒”(OP_CONNECT)
    • 一個(gè) ServerSocketChannel 服務(wù)器通道準(zhǔn)備好接收新進(jìn)入的連接,則處于“接收就緒”(OP_ACCEPT)狀態(tài)
    • 一個(gè)有數(shù)據(jù)可讀的通道,可以說是“讀就緒”(OP_READ)
    • 一個(gè)等待寫數(shù)據(jù)的通道,可以說是“寫就緒”(OP_WRITE)

1.1.4 選擇鍵(SelectionKey)

  • Channel 注冊(cè)后,并且一旦通道處于某種就緒的狀態(tài),就可以被選擇器查詢到。這個(gè)工作用選擇器 Selector 的 select() 方法完成。select 方法的作用:對(duì)感興趣的通道操作,進(jìn)行就緒狀態(tài)的查詢

  • Selector 可以不斷的查詢 Channel 中發(fā)生的操作的就緒狀態(tài),并且挑選感興趣的操作就緒狀態(tài)。一旦通道有操作的就緒狀態(tài)達(dá)成,并且是 Selector 感興趣的操作,就會(huì)被 Selector 選中,放入 選擇鍵集合

  • 一個(gè)選擇鍵,首先是包含了注冊(cè)在 Selector 的通道操作的類型,比如:SelectionKey.OP_READ。也包含了特定的通道與特定的選擇器之間的注冊(cè)關(guān)系

    • 開發(fā)應(yīng)用程序時(shí),選擇鍵是編程的關(guān)鍵。NIO 的編程,就是根據(jù)對(duì)應(yīng)的選擇鍵,進(jìn)行不同的業(yè)務(wù)邏輯處理
  • 選擇鍵的概念,和事件的概念比較相似

    • 一個(gè)選擇鍵類似監(jiān)聽器模式里邊的一個(gè)事件。由于 Selector 不是事件觸發(fā)的模式,而是主動(dòng)去查詢的模式,所以不叫事件Event,而是叫 SelectionKey 選擇鍵

1.2 Selector使用方法

1、Selector 的創(chuàng)建。

通過調(diào)用 Selector.open()方法創(chuàng)建一個(gè) Selector 對(duì)象

// 創(chuàng)建選擇器
Selector selector = Selector.open();

2、注冊(cè) Channel 到 Selector

  • 要實(shí)現(xiàn) Selector 管理 Channel,需要將 Channel 注冊(cè)到相應(yīng)的 Selector 上。通過調(diào)用通道的 register() 方法會(huì)將它注冊(cè)到一個(gè)選擇器上
// 創(chuàng)建選擇器
Selector selector = Selector.open();// 創(chuàng)建通道
ServerSocketChannel ssc = ServerSocketChannel.open();// 設(shè)置通道為非阻塞模式
ssc.configureBlocking(false);// 為通道綁定連接
ssc.bind(new InetSocketAddress(9999));// 將通道注冊(cè)到選擇器,關(guān)注接收狀態(tài)
ssc.register(selector, SelectionKey.OP_ACCEPT);

注意點(diǎn):

  • 與 Selector 一起使用時(shí),Channel 必須處于非阻塞模式下,否則將拋出異常

    IllegalBlockingModeException。FileChannel 不能切換到非阻塞模式,不能與 Selector 一起使用,而套接字相關(guān)的所有的通道都可以

  • 一個(gè)通道,并非一定要支持所有的四種操作

    • 比如:服務(wù)器通道 ServerSocketChannel 支持 Accept 接收操作,而 SocketChannel 客戶端通道則不支持。
    • 可以通過通道上的 validOps() 方法,來獲取特定通道下所有支持的操作集合,返回int值

3、輪詢查詢就緒操作

  • 通過 Selector 的 select() 方法,可以查詢出已經(jīng)就緒的通道操作,這些就緒的狀態(tài)集合,保存在 SelectionKey 對(duì)象的 Set 集合中。Selector 幾個(gè)重載的查詢 select()方法:

    • select():阻塞到至少有一個(gè)通道的注冊(cè)狀態(tài)就緒
    • select(long timeout):和 select()一樣,但最長阻塞時(shí)間為 timeout 毫秒
    • selectNow():非阻塞,不管通道的注冊(cè)狀態(tài)是否就緒就立刻返回
  • select() 方法返回的 int 值,表示有多少通道已經(jīng)就緒

    • 即兩次 select() 方法之間的時(shí)間段上,有多少通道變成就緒狀態(tài)。
    • 例如:首次調(diào)用 select()方法,如果有一個(gè)通道變成就緒狀態(tài),返回了 1,若再次調(diào)用select()方法,如果另一個(gè)通道就緒了,它會(huì)再次返回 1。如果對(duì)第一個(gè)就緒的 Channel 沒有做任何操作,現(xiàn)在就有兩個(gè)就緒的通道,但在每次 select() 方法調(diào)用之間,只有一個(gè)通道就緒了
  • 一旦調(diào)用 select() 方法,并且返回值不為 0 時(shí),在 Selector 中有一個(gè) selectedKeys() 方法,返回選擇鍵集合,迭代集合的每一個(gè)選擇鍵元素,根據(jù)就緒操作的類型,完成對(duì)應(yīng)的操作

// 查詢是否有狀態(tài)就緒
//int n = selector.select();
//int n = selector.select(2000);
int n = selector.selectNow();
System.out.println(n);// 獲取選擇鍵集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
// 遍歷選擇鍵集合(1)
for (SelectionKey key : selectionKeys) {if (key.isAcceptable()) {// 可接收System.out.println("可接收");} else if (key.isConnectable()) {// 可連接System.out.println("可連接");} else if (key.isReadable()) {// 可讀System.out.println("可讀");} else if (key.isWritable()) {// 可寫System.out.println("可寫");}
}// 遍歷選擇鍵集合(2)
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){SelectionKey key = iterator.next();if (key.isAcceptable()) {// 可接收System.out.println("可接收");} else if (key.isConnectable()) {// 可連接System.out.println("可連接");} else if (key.isReadable()) {// 可讀System.out.println("可讀");} else if (key.isWritable()) {// 可寫System.out.println("可寫");}//iterator.remove();
}

4、停止選擇的方法

選擇器執(zhí)行選擇的過程,系統(tǒng)底層會(huì)依次詢問每個(gè)通道是否已經(jīng)就緒,這個(gè)過程可能會(huì)造成調(diào)用線程進(jìn)入阻塞狀態(tài),可以有以下兩種方式可以喚醒在 select() 方法中阻塞的線程

  • wakeup():通過調(diào)用 Selector 對(duì)象的 wakeup() 方法讓處在阻塞狀態(tài)的select() 方法立刻返回。該方法使得選擇器上的第一個(gè)還沒有返回的選擇操作立即返回。如果當(dāng)前沒有進(jìn)行中的選擇操作,那么下一次對(duì) select() 方法的一次調(diào)用將立即返回

  • close():關(guān)閉 Selector。該方法使得任何一個(gè)在選擇操作中阻塞的線程都被喚醒(類似 wakeup()),同時(shí)使得注冊(cè)到該 Selector 的所有 Channel 被注銷,所有的鍵將被取消,但是 Channel 本身并不會(huì)關(guān)閉

1.3 代碼示例

服務(wù)端:

		@Testpublic void ServerDemo() throws Exception {// 1、獲取服務(wù)端通道ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();// 2、切換非阻塞模式serverSocketChannel.configureBlocking(false);// 3、綁定端口號(hào)serverSocketChannel.bind(new InetSocketAddress(8080));// 4、獲取selector選擇器Selector selector = Selector.open();// 5、通道注冊(cè)到選擇器,進(jìn)行監(jiān)聽serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);// 6、選擇器進(jìn)行輪詢,后續(xù)操作while (selector.select() > 0) {Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();while (selectionKeyIterator.hasNext()) {// 獲取就緒操作SelectionKey selectionKey = selectionKeyIterator.next();// 判斷什么操作if (selectionKey.isAcceptable()) {// 獲取連接SocketChannel accept = serverSocketChannel.accept();// 切換非阻塞模式accept.configureBlocking(false);// 注冊(cè)accept.register(selector, SelectionKey.OP_READ);} else if (selectionKey.isReadable()) {SocketChannel channel = (SocketChannel)selectionKey.channel();ByteBuffer byteBuffer = ByteBuffer.allocate(1024);// 讀取數(shù)據(jù)int length = 0;while ((length = channel.read(byteBuffer)) > 0) {byteBuffer.flip();System.out.println(new java.lang.String(byteBuffer.array(), 0, length));byteBuffer.clear();}}}selectionKeyIterator.remove();}}

客戶端:

	public static void main(String[] args) throws Exception{// 1、獲取通道,綁定主機(jī)和端口號(hào)SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8080));// 2、切換到非阻塞模式socketChannel.configureBlocking(false);// 3、創(chuàng)建bufferByteBuffer byteBuffer = ByteBuffer.allocate(1024);// 自定義輸入Scanner scanner = new Scanner(System.in);java.lang.String d = "";System.out.println("請(qǐng)輸入發(fā)送內(nèi)容...");while (scanner.hasNext()) {// 5、向緩沖區(qū)寫入數(shù)據(jù)d = "[" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-mm-dd hh:mm:ss")) + "] " + scanner.nextLine();byteBuffer.put(d.getBytes());// 6、緩沖區(qū)讀寫模式切換byteBuffer.flip();// 7、把數(shù)據(jù)從緩沖區(qū)寫入通道socketChannel.write(byteBuffer);// 清空緩沖區(qū)byteBuffer.clear();}}

注意點(diǎn):

  • 先啟動(dòng)服務(wù)端,再啟動(dòng)客戶端
  • 設(shè)置非阻塞模式、連接或綁定、創(chuàng)建緩沖區(qū);這三步順序可以調(diào)換
  • 客戶端
    • 使用connect()連接,不是bind()綁定
    • 數(shù)據(jù)寫入后不要關(guān)閉通道,否則服務(wù)端得不到客戶端通道,也就接收不到數(shù)據(jù)
    • 關(guān)閉操作應(yīng)在發(fā)生IO異常,或接收到服務(wù)端關(guān)閉通知后再進(jìn)行
  • 服務(wù)端
    • 借用select(int timeout)方法,實(shí)現(xiàn)服務(wù)端的關(guān)閉操作;在 while() 輪論后關(guān)閉
    • isAcceptable() 分支中不要關(guān)閉客戶端通道,否則后續(xù)讀取操作無法進(jìn)行
    • isReadable() 分支中也不要關(guān)閉客戶端通道,否則將不能進(jìn)行下次讀取或接收
    • 一次輪詢查詢后,要清理選擇鍵集合,為下次做準(zhǔn)備;selectionKeys.clear();

1.4 NIO 編程步驟總結(jié)

服務(wù)端:

  1. 創(chuàng)建 ServerSocketChannel 通道,并綁定監(jiān)聽端口
  2. 設(shè)置 Channel 通道是非阻塞模式
  3. 創(chuàng)建 Selector 選擇器
  4. 把 Channel 注冊(cè)到 Socketor 選擇器上,監(jiān)聽就緒狀態(tài)
  5. 調(diào)用 Selector 的 select 方法(循環(huán)調(diào)用),監(jiān)測(cè)通道的就緒狀況
  6. 調(diào)用 selectKeys 方法獲取就緒 channel 集合
  7. 遍歷就緒 channel 集合,判斷就緒事件類型,實(shí)現(xiàn)具體的業(yè)務(wù)操作
  8. 根據(jù)業(yè)務(wù),決定是否需要再次注冊(cè)監(jiān)聽事件,重復(fù)執(zhí)行第三步操作

二、Pipe和FileLock

  • Java NIO 管道是兩個(gè)線程之間的單向數(shù)據(jù)連接
  • Pipe 有一個(gè) source 通道和一個(gè)sink 通道
  • 數(shù)據(jù)會(huì)被寫到 sink 通道,從 source 通道讀取

image-20240302194335480

2.1 Pipe

  • 創(chuàng)建管道:

    // 1、獲取管道
    Pipe pipe = Pipe.open();
    
  • 寫入管道??梢匝h(huán)寫入,類似文件復(fù)制

    // 2、獲取sink通道,用來傳送數(shù)據(jù)
    Pipe.SinkChannel sink = pipe.sink();
    // 3、創(chuàng)建發(fā)送緩沖區(qū),并寫入數(shù)據(jù)
    ByteBuffer buf1 = ByteBuffer.allocate(1024);
    buf1.put("Hello World!測(cè)試".getBytes());
    // 4、讀寫模式反轉(zhuǎn)
    buf1.flip();
    // 5、sink發(fā)送數(shù)據(jù):把buf1中的數(shù)據(jù)寫入通道
    sink.write(buf1);
    
  • 從管道讀取數(shù)據(jù)。可以循環(huán)讀取,類似文件復(fù)制

    // 6、獲取source通道
    Pipe.SourceChannel source = pipe.source();
    // 7、創(chuàng)建接收緩沖區(qū)
    ByteBuffer buf2 = ByteBuffer.allocate(1024);
    // 8、讀取數(shù)據(jù),并輸出
    // 把通道中的數(shù)據(jù)讀入buf2
    int length = source.read(buf2);
    System.out.println(new String(buf2.array(),0,length));
    
  • 代碼示例:

    public class PipeDemo {public static void main(String[] args) throws IOException {// 1、獲取管道Pipe pipe = Pipe.open();// 2、獲取sink通道,用來傳送數(shù)據(jù)Pipe.SinkChannel sink = pipe.sink();// 3、創(chuàng)建發(fā)送緩沖區(qū),并寫入數(shù)據(jù)ByteBuffer buf1 = ByteBuffer.allocate(1024);buf1.put("Hello World!測(cè)試".getBytes());// 4、讀寫模式反轉(zhuǎn)buf1.flip();// 5、sink發(fā)送數(shù)據(jù):把buf1中的數(shù)據(jù)寫入通道sink.write(buf1);// 6、獲取source通道Pipe.SourceChannel source = pipe.source();// 7、創(chuàng)建接收緩沖區(qū)ByteBuffer buf2 = ByteBuffer.allocate(1024);// 8、讀取數(shù)據(jù),并輸出// 把通道中的數(shù)據(jù)讀入buf2int length = source.read(buf2);System.out.println(new String(buf2.array(),0,length));// 9、關(guān)閉source.close();sink.close();}
    }
    

2.2 FileLock

2.2.1 FileLock簡(jiǎn)介

  • 文件鎖在 OS 中很常見,如果多個(gè)程序同時(shí)訪問、修改同一個(gè)文件,很容易因?yàn)槲募?shù)據(jù)不同步而出現(xiàn)問題。給文件加一個(gè)鎖,同一時(shí)間,只能有一個(gè)程序修改此文件,或者程序都只能讀此文件,這就解決了同步問題
  • 文件鎖是進(jìn)程級(jí)別的,不是線程級(jí)別的。文件鎖可以解決多個(gè)進(jìn)程并發(fā)訪問、修改同一個(gè)文件的問題,但不能解決多線程并發(fā)訪問、修改同一文件的問題。使用文件鎖時(shí),同一進(jìn)程內(nèi)的多個(gè)線程,可以同時(shí)訪問、修改此文件
  • 文件鎖是當(dāng)前程序所屬的 JVM 實(shí)例持有的,一旦獲取到文件鎖(對(duì)文件加鎖),要調(diào)用 release(),或者關(guān)閉對(duì)應(yīng)的 FileChannel 對(duì)象,或者當(dāng)前 JVM 退出,才會(huì)釋放這個(gè)鎖
  • 一旦某個(gè)進(jìn)程(比如說 JVM 實(shí)例)對(duì)某個(gè)文件加鎖,則在釋放這個(gè)鎖之前,此進(jìn)程不能再對(duì)此文件加鎖,就是說 JVM 實(shí)例在同一文件上的文件鎖是不重疊的(進(jìn)程級(jí)別不能重復(fù)在同一文件上獲取鎖)

文件鎖分類:

  • 排它鎖獨(dú)占鎖):對(duì)文件加排它鎖后,該進(jìn)程可以對(duì)此文件進(jìn)行讀寫,該進(jìn)程獨(dú)占此文件,其他進(jìn)程不能讀寫此文件,直到該進(jìn)程釋放文件鎖

  • 共享鎖:某個(gè)進(jìn)程對(duì)文件加共享鎖,其他進(jìn)程也可以訪問此文件,但這些進(jìn)程都只能讀此文件,不能寫。線程是安全的。只要還有一個(gè)進(jìn)程持有共享鎖,此文件就只能讀,不能寫

  • 如果指定為共享鎖,則其它進(jìn)程可讀此文件,所有進(jìn)程均不能寫此文件,如果某進(jìn)程試圖對(duì)此文件進(jìn)行寫操作,會(huì)拋出異常

2.2.2 FileLock的使用

相關(guān)方法:

  • lock():對(duì)整個(gè)文件加鎖,默認(rèn)為排它鎖
  • lock(long position, long size, booean shared)
    • 前 2 個(gè)參數(shù)指定要加鎖的部分(可以只對(duì)此文件的部分內(nèi)容加鎖)

    • 第 3 個(gè)參數(shù)值指定是否是共享鎖

  • tryLock():對(duì)整個(gè)文件加鎖,默認(rèn)為排它鎖
  • tryLock(long position, long size, booean shared)
    • 前 2 個(gè)參數(shù)指定要加鎖的部分(可以只對(duì)此文件的部分內(nèi)容加鎖)
    • 第 3 個(gè)參數(shù)值指定是否是共享鎖
  • boolean isShared()
    • 判斷此文件鎖是否是共享鎖
  • boolean isValid()
    • 判斷此文件鎖是否還有效

lock 與 tryLock的區(qū)別:

  • lock 是阻塞式的,如果未獲取到文件鎖,會(huì)一直阻塞當(dāng)前線程,直到獲取文件鎖
  • tryLock 和 lock 的作用相同,只不過 tryLock 是非阻塞式的,tryLock 嘗試獲取文件鎖,獲取成功就返回鎖對(duì)象,否則返回 null,不會(huì)阻塞當(dāng)前線程

FileLock使用:

文件鎖只能通過FileChannel對(duì)象來使用

// 創(chuàng)建FileChannel對(duì)象;文件鎖只能通過FileChannel對(duì)象來使用
FileChannel fc = new FileOutputStream".\\1.txt").getChannel();// 對(duì)文件加鎖
FileLock lock = fc.lock();// 對(duì)文件進(jìn)行一些讀寫操作// 釋放鎖
lock.release();

2.2.3 完整示例

public class FileLockDemo {public static void main(String[] args) throws IOException {String input = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")) + " 測(cè)試\n";System.out.println("輸入:" + input);// 創(chuàng)建緩沖區(qū),并存入數(shù)據(jù)ByteBuffer buf = ByteBuffer.wrap(input.getBytes());// 文件路徑String filePathStr = "file/05.txt";Path path = Paths.get(filePathStr);// 文件通道FileChannel fc = FileChannel.open(path, StandardOpenOption.APPEND);// position位置if(fc.size()!=0){fc.position(fc.size() - 1);}// 文件鎖// 1、阻塞模式;默認(rèn)為獨(dú)占鎖FileLock lock = fc.lock();// 2、阻塞模式;共享鎖;只能讀,不能寫;寫時(shí)會(huì)拋異常// FileLock lock = fc.lock(0,Long.MAX_VALUE,true);// 3、非阻塞模式;默認(rèn)為獨(dú)占鎖;獲取不到鎖時(shí),返回null,不阻塞// FileLock lock = fc.tryLock();// 4、非阻塞模式;共享鎖;只能讀,不能寫;寫時(shí)會(huì)拋異常// FileLock lock = fc.tryLock(0, Long.MAX_VALUE, true);System.out.println("是否為共享鎖:" + lock.isShared());// 把buf中的數(shù)據(jù)寫入文件通道fc.write(buf);// 關(guān)閉fc.close();System.out.println("寫操作完成!");// 讀取數(shù)據(jù)System.out.println("=========流讀取==========");readFileByStream(filePathStr);System.out.println("=========通道讀取==========");readFileByChannel(filePathStr);}/*** 流讀取* @param filePathStr* @throws IOException*/private static void readFileByStream(String filePathStr) throws IOException {FileReader fr = new FileReader(filePathStr);BufferedReader br = new BufferedReader(fr);String line = "";while((line = br.readLine()) !=null){System.out.println(line);}br.close();}/*** 通道讀取* @param filePathStr* @throws IOException*/private static void readFileByChannel(String filePathStr) throws IOException {FileInputStream fis = new FileInputStream(filePathStr);FileChannel fc = fis.getChannel();ByteBuffer buf = ByteBuffer.allocate(1024);int length = 0;while((length = fc.read(buf)) > 0 ){// buf.flip();System.out.println(new String(buf.array(),0,length));buf.clear();}fc.close();}
}

三、其它

3.1 Path

3.1.1 Path簡(jiǎn)介

image-20240310141734725

  • Java Path 接口是 Java NIO 更新的一部分,同 Java NIO 一起已經(jīng)包括在 Java6 和 Java7 中。Java Path 接口是在 Java7 中添加到 Java NIO 的。Path 接口位于java.nio.file 包中,所以 Path 接口的完全限定名稱為java.nio.file.Path
  • Java Path 實(shí)例表示文件系統(tǒng)中的路徑。一個(gè)路徑可以指向一個(gè)文件或一個(gè)目錄。路徑可以是絕對(duì)路徑,也可以是相對(duì)路徑。絕對(duì)路徑包含從文件系統(tǒng)的根目錄到它指向的文件或目錄的完整路徑。相對(duì)路徑包含相對(duì)于其他路徑的文件或目錄的路徑
  • 在許多方面,java.nio.file.Path 接口類似于 java.io.File 類,但是有一些差別。不過,在許多情況下,可以使用 Path 接口來替換 File 類的使用

3.1.2 創(chuàng)建Path實(shí)例

  • 使用 java.nio.file.Path 實(shí)例必須創(chuàng)建一個(gè) Path 實(shí)例??梢允褂?Paths 類 (java.nio.file.Paths) 中的靜態(tài)方法 Paths.get() 來創(chuàng)建路徑實(shí)例。Paths.get() 方法相當(dāng)于是 Path 實(shí)例的工廠方法

    Path path = Paths.get(".\\src\\main\\resources\\06.txt");
    

創(chuàng)建絕對(duì)路徑:

// windows系統(tǒng)
Path path1 = Paths.get("d:\\01.txt");// Linux、MacOS系統(tǒng)
Path path2 = Paths.get("/home/jakobjenkov/myfile.txt");

在 Java 字符串中, \是一個(gè)轉(zhuǎn)義字符,需要編寫\,告訴 Java 編譯器在字符串中寫入一個(gè)\字符。如果在 Windows 機(jī)器上使用了從/開始的路徑,那么路徑將被解釋為相對(duì)于當(dāng)前驅(qū)動(dòng)器

創(chuàng)建相對(duì)路徑:

  • 使用 Paths.get(basePath,relativePath)方法創(chuàng)建一個(gè)相對(duì)路徑
// 相對(duì)路徑:d:\abc\xyz
Path path2 = Paths.get("d:\\abc", "xyz");
// 相對(duì)路徑:d:\abc\xyz\03.txt
Path path3 = Paths.get("d:\\abc", "xyz\\03.txt");

3.2 Files

  • Java NIO Files 類(java.nio.file.Files)提供了幾種操作文件系統(tǒng)中的文件的方法。通常與java.nio.file.Path 實(shí)例一起工作

常用方法:

Modifier and TypeMethod and Description
static longcopy(InputStream in, Path target, CopyOption... options)將輸入流中的所有字節(jié)復(fù)制到文件。
static longcopy(Path source, OutputStream out)將文件中的所有字節(jié)復(fù)制到輸出流。
static Pathcopy(Path source, Path target, CopyOption... options)將文件復(fù)制到目標(biāo)文件。
static PathcreateDirectories(Path dir, FileAttribute<?>... attrs)首先創(chuàng)建所有不存在的父目錄來創(chuàng)建目錄。
static PathcreateDirectory(Path dir, FileAttribute<?>... attrs)創(chuàng)建一個(gè)新的目錄。
static PathcreateFile(Path path, FileAttribute<?>... attrs)創(chuàng)建一個(gè)新的和空的文件,如果該文件已存在失敗。
static PathwalkFileTree(Path start, FileVisitor<? super Path> visitor)走一個(gè)文件樹。
static PathwalkFileTree(Path start, Set<FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor)走一個(gè)文件樹。

3.2.1 Files.createDirectory()

  • Files.createDirectory()方法,用于根據(jù) Path 實(shí)例創(chuàng)建一個(gè)新目錄
    @Testpublic void testCreateDirectory() {// createDirectoryPath path = Paths.get("./src/main/resources/abc");// 創(chuàng)建能目錄try {Files.createDirectory(path);}catch (FileAlreadyExistsException e) {System.out.println("目錄已存在"+ e);} catch (IOException e) {throw new RuntimeException(e);}}

第一行創(chuàng)建表示要?jiǎng)?chuàng)建的目錄的 Path 實(shí)例。在 try-catch 塊中,用路徑作為參數(shù)調(diào)用Files.createDirectory()方法。如果創(chuàng)建目錄成功,將返回一個(gè) Path 實(shí)例,該實(shí)例指向新創(chuàng)建的路徑。

如果該目錄已經(jīng)存在,則是拋出一個(gè) java.nio.file.FileAlreadyExistsException。如果出現(xiàn)其他錯(cuò)誤,可能會(huì)拋出 IOException。例如,如果想要的新目錄的父目錄不存在,則可能會(huì)拋出 IOException

3.2.2 Files.copy()

  • copy(InputStream in, Path target, CopyOption... options)將輸入流中的所有字節(jié)復(fù)制到文件。

不覆蓋:

  • 從一個(gè)路徑拷貝一個(gè)文件到另外一個(gè)目錄如果目標(biāo)文件已經(jīng)存在,則拋出一個(gè) java.nio.file.FileAlreadyExistsException 異常。如果有其他錯(cuò)誤,則會(huì)拋出一個(gè) IOException。例如,如果將該文件復(fù)制到不存在的目錄,則會(huì)拋出 IOException

            Path path1 = Paths.get(".\\src\\main\\resources\\1.wav");Path path2 = Paths.get(".\\src\\main\\resources\\abc\\1.wav");try {Path copy = Files.copy(path1, path2);} catch (FileAlreadyExistsException e) {// 目錄已存在} catch (IOException e) {e.printStackTrace();}
    

覆蓋:

  • **Path copy = Files.copy(path1, path2, StandardCopyOption.REPLACE_EXISTING);**如果目標(biāo)文件已經(jīng)存在,這個(gè)參數(shù)指示 copy() 方法覆蓋現(xiàn)有的文件

3.2.3 Files.move()

  • 移動(dòng)文件 或者 重命名,move() 之后原文件不存在。Files.move()的第三個(gè)參數(shù)。這個(gè)參數(shù)告訴 Files.move()方法來覆蓋目標(biāo)路徑上的任何現(xiàn)有文件
    @Testpublic void testMove() {Path path1 = Paths.get("./src/main/resources/01.txt");Path path2 = Paths.get("./src/main/resources/11.txt");try {Path move = Files.move(path1, path2);} catch (FileAlreadyExistsException e) {// 文件已存在} catch (IOException e) {e.printStackTrace();}}

3.2.4 Files.delete()

  • 刪除一個(gè)文件或者目錄
    @Testpublic void testDelete() {Path path = Paths.get("./src/main/resources/01.txt");try {Files.delete(path);} catch (IOException e) {e.printStackTrace();}}

3.2.5 Files.walkFileTree()

  • Files.walkFileTree() 方法包含遞歸遍歷目錄樹功能,將 Path 實(shí)例和 FileVisitor 作為參數(shù)。Path 實(shí)例指向要遍歷的目錄,FileVisitor 在遍歷期間被調(diào)用

  • FileVisitor 是一個(gè)接口,必須自己實(shí)現(xiàn) FileVisitor 接口,并將實(shí)現(xiàn)的實(shí)例傳遞給 walkFileTree() 方法。在目錄遍歷過程中,FileVisitor 實(shí)現(xiàn)的每個(gè)方法都將被調(diào)用。如果不需要實(shí)現(xiàn)所有這些方法,那么可以擴(kuò)展 SimpleFileVisitor 類,它包含F(xiàn)ileVisitor 接口中所有方法的默認(rèn)實(shí)現(xiàn)(適配器模式)

  • FileVisitor 接口的方法中,每個(gè)都返回一個(gè) FileVisitResult 枚舉實(shí)例。

    FileVisitResult 枚舉包含以下四個(gè)選項(xiàng):

    • CONTINUE:繼續(xù)
    • TERMINATE:終止
    • SKIP_SIBLING:跳過同級(jí)
    • SKIP_SUBTREE:跳過子級(jí)
		@Testpublic void walkFileTree(){// 要查找的目錄范圍Path path = Paths.get("./src/main/resources");// 要查找的目標(biāo)文件//String findFile = File.separator + "03.txt";String findFile = "01.txt";// 是否找到final boolean[] isExist = {false};try {Path path1 = Files.walkFileTree(path, new SimpleFileVisitor<Path>() {/*** 重載內(nèi)部類方法* @param file* @param attrs* @return* @throws IOException*/@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {String fileString = file.toAbsolutePath().toString();// 如果目標(biāo)文件和路徑結(jié)尾相等,即為找到if (fileString.endsWith(findFile)) {// 把查找標(biāo)記為trueisExist[0] = true;System.out.println("文件已找到!路徑為:" + fileString);// 中止退出return FileVisitResult.TERMINATE;}// 繼續(xù)下次查找return FileVisitResult.CONTINUE;}});if(!isExist[0]){System.out.println("沒有找到!");}} catch (IOException e) {e.printStackTrace();}}

3.3 AsynchronousFileChannel

在 Java 7 中,Java NIO 中添加了 AsynchronousFileChannel,也就是是異步地將數(shù)據(jù)寫入文件。

3.3.1 創(chuàng)建AsynchronousFileChannel

  • 通過靜態(tài)方法 open()創(chuàng)建,AsynchronousFileChannel.open(path,StandardOpenOption.READ);
// 1、得到文件路徑
Path path = Paths.get(".\\src\\main\\resources\\03.txt");
// 2、創(chuàng)建AsynchronousFileChannel
AsynchronousFileChannel afc = null;
try {afc = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
} catch (IOException e) {e.printStackTrace();
}

3.3.2 通過Future讀取數(shù)據(jù)

**abstract Future<Integer> read(ByteBuffer dst, long position)**從給定的文件位置開始,從該通道讀取一個(gè)字節(jié)序列到給定的緩沖區(qū)。

    @Testpublic void testReadAsyncFileChannel() throws IOException {// 1、得到文件路徑Path path = Paths.get("./src/main/resources/03.txt");// 2、創(chuàng)建AsynchronousFileChannelAsynchronousFileChannel afc = null;try {afc = AsynchronousFileChannel.open(path, StandardOpenOption.READ);} catch (IOException e) {e.printStackTrace();}// 3、創(chuàng)建緩沖區(qū)ByteBuffer buf = ByteBuffer.allocate(1024);// 4、把通道中的數(shù)據(jù)讀到緩沖區(qū),得到FutureFuture<Integer> future = afc.read(buf, 0);// 5、判斷是否讀取完成while (!future.isDone()) {}// 6、輸出buf.flip();System.out.println(new String(buf.array(), 0, buf.limit()));buf.clear();// 7、關(guān)閉afc.close();}
  1. 創(chuàng)建了一個(gè) AsynchronousFileChannel
  2. 創(chuàng)建一個(gè) ByteBuffer,它被傳遞給 read() 方法作為參數(shù),以及一個(gè) 0 的位置
  3. 在調(diào)用 read() 之后,循環(huán),直到返回的 isDone()方法返回 true
  4. 讀取操作完成后,數(shù)據(jù)讀取到 ByteBuffer 中,然后打印到 System.out 中

3.3.3 通過CompletionHandler讀取數(shù)據(jù)

**abstract <A> void read(ByteBuffer dst, long position, A attachment, CompletionHandler<Integer,? super A> handler)**從給定的文件位置開始,從該通道讀取一個(gè)字節(jié)序列到給定的緩沖區(qū)。

    @Testpublic void testCompletionHandlerRead() {// 1、得到文件路徑Path path = Paths.get("./src/main/resources/03.txt");// 2、創(chuàng)建AsynchronousFileChannelAsynchronousFileChannel afc = null;try {afc = AsynchronousFileChannel.open(path, StandardOpenOption.READ);} catch (IOException e) {e.printStackTrace();}// 3、創(chuàng)建緩沖區(qū)ByteBuffer buf = ByteBuffer.allocate(1024);// 4、把通道中的數(shù)據(jù)讀到緩沖區(qū)afc.read(buf, 0, buf, new CompletionHandler<Integer, ByteBuffer>() {/*** 讀取完成* @param result* @param attachment*/@Overridepublic void completed(Integer result, ByteBuffer attachment) {System.out.println("讀取到:" + result);attachment.flip();System.out.println(new String(attachment.array(),0,attachment.limit()));attachment.clear();}/*** 讀取失敗* @param exc* @param attachment*/@Overridepublic void failed(Throwable exc, ByteBuffer attachment) {System.out.println("讀取失敗!");}});}
  1. 讀取操作完成,將調(diào)用 CompletionHandler 的 completed() 方法
  2. 對(duì)于 completed() 方法的參數(shù)傳遞一個(gè)整數(shù),它告訴我們讀取了多少字節(jié),以及傳遞給 read() 方法的“附件”?!案郊笔?read()方法的第三個(gè)參數(shù)。在本代碼中,它是 ByteBuffer,數(shù)據(jù)也被讀取
  3. 如果讀取操作失敗,則將調(diào)用 CompletionHandler 的 failed() 方法

3.3.4 通過Future寫數(shù)據(jù)

image-20240310144802521

    @Testpublic void testWriteAsyncFileFuture() throws IOException {// 1、得到文件路徑Path path = Paths.get("./src/main/resources/04.txt");// 2、創(chuàng)建AsynchronousFileChannelAsynchronousFileChannel afc = null;try {afc = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);} catch (IOException e) {e.printStackTrace();}// 3、創(chuàng)建緩沖區(qū)//        ByteBuffer buf = ByteBuffer.allocate(1024);//        buf.put("測(cè)試abc".getBytes());//        buf.flip();ByteBuffer buf = ByteBuffer.wrap("測(cè)試testWriteAsyncFileFuture".getBytes());// 4、把緩沖區(qū)中的數(shù)據(jù)寫入通道,得到FutureFuture<Integer> future = afc.write(buf, 0);// 5、判斷是否寫入完成while (!future.isDone()) {}// 6、輸出System.out.println("寫入完成!");// 7、關(guān)閉afc.close();}
  • AsynchronousFileChannel 以寫模式打開
  • 創(chuàng)建一個(gè) ByteBuffer,并將一些數(shù)據(jù)寫入其中
  • ByteBuffer 中的數(shù)據(jù)被寫入到文件中
  • 檢查返回的 Future,以查看寫操作完成時(shí)的情況

注意,文件必須已經(jīng)存在。如果該文件不存在,那么 write()方法將拋出一個(gè)

java.nio.file.NoSuchFileException

3.3.5 通過CompletionHandler寫數(shù)據(jù)

image-20240310144823208

    @Testpublic void testWriteAsyncFileCompletion() {// 1、得到文件路徑Path path = Paths.get("./src/main/resources/04.txt");// 2、創(chuàng)建AsynchronousFileChannelAsynchronousFileChannel afc = null;try {afc = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);} catch (IOException e) {e.printStackTrace();}// 3、創(chuàng)建緩沖區(qū)ByteBuffer buf = ByteBuffer.allocate(1024);buf.put("測(cè)試testWriteAsyncFileCompletion".getBytes());buf.flip();// 4、把緩沖區(qū)中的數(shù)據(jù)寫入通道afc.write(buf, 0, null, new CompletionHandler<Integer, ByteBuffer>() {/*** 寫入完成* @param result* @param attachment*/@Overridepublic void completed(Integer result, ByteBuffer attachment) {System.out.println("寫入:" + result);}/*** 寫入失敗* @param exc* @param attachment*/@Overridepublic void failed(Throwable exc, ByteBuffer attachment) {System.out.println("寫入失敗!");}});}
  • 寫操作完成時(shí),將會(huì)調(diào)用 CompletionHandler 的 completed()方法
  • 如果寫失敗,則會(huì)調(diào)用 failed()方法

3.4 字符集(Charset)

    public static void main(String[] args) throws CharacterCodingException {// 1、獲取Charset對(duì)象Charset charset = Charset.forName("utf8");// 2、建立緩沖區(qū),準(zhǔn)備數(shù)據(jù)CharBuffer buf = CharBuffer.allocate(1024);buf.put("測(cè)試abc");buf.flip();// 3、獲取新的編碼器CharsetEncoder charsetEncoder = charset.newEncoder();// 4、編碼ByteBuffer byteBuffer = charsetEncoder.encode(buf);System.out.println("編碼后:");for (int i = 0; i < byteBuffer.limit(); i++) {System.out.println(byteBuffer.get());}// 5、獲取新的解碼器byteBuffer.flip();CharsetDecoder charsetDecoder = charset.newDecoder();CharBuffer decoderBuf = charsetDecoder.decode(byteBuffer);System.out.println("解碼后:");System.out.println(decoderBuf);// 7、用其它字符格式解碼Charset gbkChar = Charset.forName("gbk");byteBuffer.flip();System.out.println("使用其他編碼進(jìn)行解碼:");System.out.println(gbkChar.decode(byteBuffer));// 8、獲取Charset所支持的字符編碼Map<String, Charset> scssm = Charset.availableCharsets();Set<Map.Entry<String, Charset>> entries = scssm.entrySet();for (Map.Entry<String, Charset> entry : entries) {System.out.println(entry + " : " + entry.getValue());}}

四、多人聊天室

image-20240310145517265

image-20240310150125897

4.1 服務(wù)端

超過 timeout 毫秒沒有連接,關(guān)閉服務(wù)端

public class ChatServer {public static void main(String[] args) throws IOException {new ChatServer().startServer();}/*** 服務(wù)器端啟動(dòng)的方法*/public void startServer() throws IOException {// 1、創(chuàng)建Selector選擇器Selector selector = Selector.open();// 2、創(chuàng)建服務(wù)端通道ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();// 3、為channel通道綁定端口、設(shè)置為非阻塞模式serverSocketChannel.bind(new InetSocketAddress(8000));serverSocketChannel.configureBlocking(false);// 4、把channel通道注冊(cè)到選擇器serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("服務(wù)器已啟動(dòng)成功!");// 5、循環(huán)查詢就緒狀態(tài)while(selector.select() > 0){// 6、得到選擇鍵集合,并遍歷Set<SelectionKey> selectionKeys = selector.selectedKeys();for (SelectionKey sk : selectionKeys) {// 6.1、可接收狀態(tài):表示服務(wù)端已做好準(zhǔn)備,可以接收客戶的連接了if(sk.isAcceptable()){// 處理可接收時(shí)的操作acceptOperator(serverSocketChannel,selector);}// 6.2、可讀狀態(tài):表示客戶端已發(fā)送完畢,可以在服務(wù)端讀取數(shù)據(jù)了if(sk.isReadable()){// 處理可讀時(shí)的操作readOperator(selector,sk);}}// 清理選擇鍵集合:為下次輪詢查詢做準(zhǔn)備selectionKeys.clear();}}/*** 可接收狀態(tài)時(shí)的處理操作* @param serverSocketChannel 服務(wù)端通道* @param selector 選擇器* @throws IOException*/private static void acceptOperator(ServerSocketChannel serverSocketChannel,Selector selector) throws IOException {// 1、獲取客戶端通道 SocketChannelSocketChannel sc = serverSocketChannel.accept();// 2、設(shè)置為非阻塞模式sc.configureBlocking(false);// 3、把通道注冊(cè)到選擇器上,監(jiān)聽可讀狀態(tài)sc.register(selector,SelectionKey.OP_READ);// 4、回復(fù)客戶端ByteBuffer replyStr = Charset.forName("utf8").encode("歡迎進(jìn)入聊天室!");sc.write(replyStr);}/*** 可讀狀態(tài)時(shí)的處理操作* @param selector 選擇器* @param sk 選擇鍵* @throws IOException*/private static void readOperator(Selector selector,SelectionKey sk) throws IOException {// 1、從選擇鍵SelectionKey獲取已經(jīng)就緒的客戶端通道SocketChannel socketChannel = (SocketChannel) sk.channel();// 2、創(chuàng)建BufferByteBuffer buf = ByteBuffer.allocate(1024);// 3、循環(huán)讀取客戶端消息String message = "";while(socketChannel.read(buf) > 0){// 切換buf讀寫模式// 調(diào)用 flip()方法會(huì)將 position 設(shè)回 0,并將 limit 設(shè)置成之前 position 的值:即readLengthbuf.flip();message += Charset.forName("utf8").decode(buf);}// 4、把通道再次注冊(cè)到選擇器,監(jiān)聽可讀狀態(tài)socketChannel.register(selector,SelectionKey.OP_READ);// 5、把客戶端消息廣播到其它客戶端if(message.length() > 0){System.out.println(message);castOtherClient(message,selector,socketChannel);}}/*** 給其它客戶端廣播消息* @param message 消息* @param selector 選擇器* @param sc 自已的通道* @throws IOException*/private static void castOtherClient(String message,Selector selector,SocketChannel sc) throws IOException {// 1、獲取所有已經(jīng)接入的通道的選擇鍵Set<SelectionKey> keys = selector.keys();// 2、循環(huán)遍歷:找出除了自已之外的其它客戶端通道,并發(fā)送消息for (SelectionKey key : keys) {//System.out.println(key);// 獲取當(dāng)前選擇鍵的通道Channel targetChannel = key.channel();// 向除了自已之外的其它客戶端通道發(fā)送消息if(targetChannel instanceof SocketChannel && targetChannel != sc){// 發(fā)送消息((SocketChannel)targetChannel).write(Charset.forName("utf8").encode(message));}}}}

4.2 客戶端

  • ChatClient.java

Scanner 會(huì)阻塞等待

public class ChatClient {/*** 啟動(dòng)方法*/public void startClient(String name) {System.out.print(name + ",你好,");SocketChannel sc = null;try {// 1、創(chuàng)建選擇器Selector selector = Selector.open();// 2、創(chuàng)建客戶端通道,連接服務(wù)端sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8000));// 3、設(shè)置為非阻塞模式sc.configureBlocking(false);// 4、把通道注冊(cè)到選擇器sc.register(selector, SelectionKey.OP_READ);// 5、創(chuàng)建新線程,接收消息new Thread(new ClientThread(selector)).start();// 6、向服務(wù)端發(fā)送消息Scanner scanner = new Scanner(System.in);String msg = "";while (scanner.hasNextLine()) {msg = scanner.nextLine();if (msg.length() > 0) {sc.write(Charset.forName("utf8").encode(name + ":" + msg));}}} catch (IOException e) {//e.printStackTrace();} finally {// 關(guān)閉try {if (sc != null) {sc.close();}System.out.println("客戶端已關(guān)閉!");} catch (IOException e) {e.printStackTrace();}}}
}
  • ClientThread.java
public class ClientThread implements Runnable{/*** 選擇器*/private Selector selector;/*** 構(gòu)造器* @param selector*/public ClientThread(Selector selector){this.selector = selector;}@Overridepublic void run() {try{while(selector.select() > 0){// 得到選擇鍵集合,并遍歷Set<SelectionKey> selectionKeys = selector.selectedKeys();for (SelectionKey sk : selectionKeys) {// 可讀狀態(tài):表示可以讀取服務(wù)器端發(fā)送的數(shù)據(jù)了if(sk.isReadable()){// 處理可讀時(shí)的操作readOperator(selector,sk);}}// 清理選擇鍵集合:為下次輪詢查詢做準(zhǔn)備selectionKeys.clear();}}catch (IOException e){e.printStackTrace();}}/*** 可讀狀態(tài)時(shí)的處理操作* @param selector 選擇器* @param sk 選擇鍵* @throws IOException*/private void readOperator(Selector selector,SelectionKey sk) throws IOException {// 1、從選擇鍵SelectionKey獲取已經(jīng)就緒的客戶端通道SocketChannel sc = (SocketChannel) sk.channel();// 2、創(chuàng)建BufferByteBuffer buf = ByteBuffer.allocate(1024);// 3、循環(huán)讀取客戶端消息String message = "";while(sc.read(buf) > 0){// 切換buf讀寫模式// 調(diào)用 flip()方法會(huì)將 position 設(shè)回 0,并將 limit 設(shè)置成之前 position 的值:即readLengthbuf.flip();message += Charset.forName("utf8").decode(buf);}// 4、把通道再次注冊(cè)到選擇器,監(jiān)聽可讀狀態(tài)。好像不用再次注冊(cè)sc.register(selector,SelectionKey.OP_READ);// 5、輸出消息if(message.length() > 0){System.out.println(message);}}
}
  • 模擬聊天用戶,每個(gè)用戶是獨(dú)立的;startClient() 不能用靜態(tài)方法,否則將共用客戶端通道

    • 用戶A

      public class AClient {public static void main(String[] args) {new ChatClient().startClient("lucy");}}
      
    • 用戶B

      public class BClient {public static void main(String[] args) {new ChatClient().startClient("mack");}
      }
      
http://www.risenshineclean.com/news/54114.html

相關(guān)文章:

  • 江門網(wǎng)站制作維護(hù)南昌seo公司
  • 政府網(wǎng)站的建設(shè)規(guī)劃設(shè)想seo關(guān)鍵詞排名優(yōu)化哪家好
  • 域名注冊(cè)了 如何做網(wǎng)站如何推廣app賺錢
  • 機(jī)械網(wǎng)站優(yōu)化百度推廣客戶端怎么登陸
  • 最新開的手游傳奇網(wǎng)站seo研究中心晴天
  • 集團(tuán)簡(jiǎn)介ppt優(yōu)秀范例百度網(wǎng)站怎么優(yōu)化排名
  • 怎樣做企業(yè)網(wǎng)站建設(shè)網(wǎng)站推廣與優(yōu)化平臺(tái)
  • 建設(shè)部網(wǎng)站公示公告安全seo推廣的全稱是
  • 怎樣開通網(wǎng)站網(wǎng)站開發(fā)的一般流程
  • 濟(jì)南 網(wǎng)站建設(shè)公司 醫(yī)療湖南關(guān)鍵詞排名推廣
  • 做oa好 還是做網(wǎng)站好網(wǎng)絡(luò)推廣的平臺(tái)有哪些
  • 臨沂市建設(shè)安全管理網(wǎng)站整合營銷的最高階段是
  • 網(wǎng)站首頁菜單欄表怎么做中國今天最新軍事新聞
  • 數(shù)據(jù)來源于網(wǎng)站需如何做腳注谷歌推廣怎么樣
  • 深圳營銷型網(wǎng)站建設(shè)公司本周的新聞大事10條
  • 服務(wù)器 打開網(wǎng)站iis7下載百度2023最新版
  • 西安做網(wǎng)站的公司排名西安seo托管
  • wordpress503原因seo短期課程
  • 創(chuàng)造網(wǎng)站黃頁污水
  • 正規(guī)的品牌網(wǎng)站建設(shè)服務(wù)軟件外包網(wǎng)
  • 品牌做網(wǎng)站公司長沙網(wǎng)站seo服務(wù)
  • 成都網(wǎng)站制作公司有哪些百度公司怎么樣
  • 讀書網(wǎng)站建設(shè)策劃書摘要百度投訴熱線中心客服
  • 網(wǎng)站建設(shè)順序seo關(guān)鍵詞優(yōu)化軟件怎么樣
  • 工體網(wǎng)站建設(shè)公司論述搜索引擎優(yōu)化的具體措施
  • 我想注冊(cè)網(wǎng)站我怎么做廊坊seo排名霸屏
  • 網(wǎng)站是如何建立的網(wǎng)絡(luò)營銷推廣seo
  • 網(wǎng)站的建設(shè)及維護(hù)報(bào)告優(yōu)化營商環(huán)境
  • wordpress代碼上傳到服務(wù)器深圳優(yōu)化seo排名
  • 網(wǎng)站頁面怎么做的好看chatgpt 鏈接