杭州做網(wǎng)站外包公司哪家好優(yōu)化seo網(wǎng)站
一、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ò)鏈接。
使用 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 感興趣的
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ù)端:
- 創(chuàng)建 ServerSocketChannel 通道,并綁定監(jiān)聽端口
- 設(shè)置 Channel 通道是非阻塞模式
- 創(chuàng)建 Selector 選擇器
- 把 Channel 注冊(cè)到 Socketor 選擇器上,監(jiān)聽就緒狀態(tài)
- 調(diào)用 Selector 的 select 方法(循環(huán)調(diào)用),監(jiān)測(cè)通道的就緒狀況
- 調(diào)用 selectKeys 方法獲取就緒 channel 集合
- 遍歷就緒 channel 集合,判斷就緒事件類型,實(shí)現(xiàn)具體的業(yè)務(wù)操作
- 根據(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 通道讀取
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)介
- 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 Type | Method and Description |
---|---|
static long | copy(InputStream in, Path target, CopyOption... options) 將輸入流中的所有字節(jié)復(fù)制到文件。 |
static long | copy(Path source, OutputStream out) 將文件中的所有字節(jié)復(fù)制到輸出流。 |
static Path | copy(Path source, Path target, CopyOption... options) 將文件復(fù)制到目標(biāo)文件。 |
static Path | createDirectories(Path dir, FileAttribute<?>... attrs) 首先創(chuàng)建所有不存在的父目錄來創(chuàng)建目錄。 |
static Path | createDirectory(Path dir, FileAttribute<?>... attrs) 創(chuàng)建一個(gè)新的目錄。 |
static Path | createFile(Path path, FileAttribute<?>... attrs) 創(chuàng)建一個(gè)新的和空的文件,如果該文件已存在失敗。 |
static Path | walkFileTree(Path start, FileVisitor<? super Path> visitor) 走一個(gè)文件樹。 |
static Path | walkFileTree(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();}
- 創(chuàng)建了一個(gè) AsynchronousFileChannel
- 創(chuàng)建一個(gè) ByteBuffer,它被傳遞給 read() 方法作為參數(shù),以及一個(gè) 0 的位置
- 在調(diào)用 read() 之后,循環(huán),直到返回的 isDone()方法返回 true
- 讀取操作完成后,數(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("讀取失敗!");}});}
- 讀取操作完成,將調(diào)用 CompletionHandler 的 completed() 方法
- 對(duì)于 completed() 方法的參數(shù)傳遞一個(gè)整數(shù),它告訴我們讀取了多少字節(jié),以及傳遞給 read() 方法的“附件”?!案郊笔?read()方法的第三個(gè)參數(shù)。在本代碼中,它是 ByteBuffer,數(shù)據(jù)也被讀取
- 如果讀取操作失敗,則將調(diào)用 CompletionHandler 的 failed() 方法
3.3.4 通過Future寫數(shù)據(jù)
@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ù)
@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());}}
四、多人聊天室
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");} }
-