1. 程式人生 > >[瘋狂Java]NIO.2:WatchService、WatchKey(監控檔案變化)

[瘋狂Java]NIO.2:WatchService、WatchKey(監控檔案變化)

1. 舊版本監控檔案變化的弊端:

    1) 非常繁瑣,必須自己手動開啟一個後臺執行緒每隔一段時間遍歷一次目標節點並記錄當前狀態,然後和上一次遍歷的狀態對比,如果不相同就表示發生了變化,再採取相應的操作,這個過程非常長,都需要使用者自己手動實現;

    2) 效率低:效率都消耗在了遍歷、儲存狀態、對比狀態上了!這是因為舊版本的Java無法很好的利用OS檔案系統的功能,因此只能這樣笨拙地監控檔案變化;

    3) 無法利用OS的很多功能:上一點已經闡明;

2. WatchService:

    1) 該類的物件就是作業系統原生的檔案系統監控器!我們都知道OS自己的檔案系統監控器可以監控系統上所有檔案的變化,這種監控是無需遍歷、無需比較的,是一種基於訊號收發的監控,因此效率一定是最高的;現在Java對其進行了包裝,可以直接在Java程式中使用OS的檔案系統監控器了;

    2) 獲取當前OS平臺下的檔案系統監控器:

         i. WatchService watcher = FileSystems.getDefault().newWatchService();

         ii. 從FileSystems這個類名就可以看出這肯定是屬於OS平臺檔案系統的,接下來可以看出這一連串方法直接可以得到一個檔案監控器;

!!這裡暫時不用深入理解這串方法的具體含義,先知道怎麼用就行了;

    3) 我們都知道,作業系統上可以同時開啟多個監控器,因此在Java程式中也不例外,上面的程式碼只是獲得了一個監控器,你還可以用同樣的程式碼同時獲得多個監控器;

    4) 監控器其實就是一個後臺執行緒,在後臺監控檔案變化所發出的訊號,這裡通過上述程式碼獲得的監控器還只是一個剛剛初始化的執行緒,連就緒狀態都沒有進入,只是初始化而已;

3. 將監控器註冊給指定的檔案節點——開啟監控執行緒來監控指定的節點:

    1) 首先監控需要有個目標吧,你需要指定監控哪個檔案節點;

    2) 其次就是檔案變化的種類有很多,有建立、刪除、修改等多種型別的檔案變化;

    3) Path物件的register方法搞定一切:

         i. 原型:WatchKey Path.register(WatchService watcher, WatchEvent.Kind<?>... events);

         ii. 其意義很明確,就是將指定的監控器註冊給Path物件所代表的檔案節點,而events則指定了監控器監控哪些型別的檔案變化;

         iii. 該方法同時也會讓監控器執行緒就緒並執行,該方法呼叫完後監控器就徹底開始監控了!!

    4) WatchEvent.Kind<?> events:

         i. 一看到Kind就知道該類一定是一個列舉類,裡面定義了檔案變化的各種列舉型別;

         ii. 這些列舉變數都定義在StandardWatchEventKinds中,主要有三個:

ENTRY_CREATE:建立

ENTRY_DELETE:刪除

ENTRY_MODIFY:修改

    5) WatchKey:

         i. 即監控鍵,說的明確一點就是該檔案節點所繫結的監控器的監控資訊池,即檔案節點的監控池,簡稱監控池;

         ii. 所有監控到的資訊都會放到監控池中;

         iii. register方法返回的就是節點的監控池;

    6) 監控池是靜態的!!!

         i. 監控池只能表示某一個時間節點下的檔案變化資訊,並不能動態儲存這些資訊;像register方法,剛註冊完是返回的監控池是一個空的監控池,因為剛剛開啟執行緒,什麼都還沒有發生,即使後面發生了檔案修改,那麼該監控池物件的內容還是保持不變,仍然是空的;

!!Java要求,只有當你主動去獲取新的監控池時才會將更新的內容放入獲取到的監控池中!

!!這是因為應用程式往往有這樣的需求,就是當檔案發生變化的這個時間節點上進行一定的處理,那何不就剛好在這個時間點上去更新監控池呢?

!!當然Java也完全可以做到實時更新監控池,這樣的話register返回的那個監控池就會實時變化,可以一直用到底,但是你要想,WatchService其實是作業系統的執行緒,而監控池是Java程式內的記憶體空間,要將檔案變化實時反映出來那就需要Java程序和作業系統的監控程序實時交流才行,這樣豈不是在程序間通訊的時間會花費很多,那還是乾脆只有在檔案發生變化的時間點上程式通過主動獲取監控池來將監控資訊傳入Java程式來的省時省力;

         ii. 獲取下一個監控資訊:其實就是獲取新的監控池

             a. WatchKey WatchService.poll(); // 嘗試獲取下一個變化資訊的監控池,如果沒有變化則返回null

             b. WatchKey WatchService.take(); // // 嘗試獲取下一個變化資訊的監控池,如果沒有變化則一直等待

!!這兩個方法都是WatchService的物件方法,從這點可以看出,獲取更新的監控資訊其實需要當前Java程序和作業系統程序之間進行交流,返回的WatchKey是當前Java程式的(記憶體空間位於當前Java程式中);

         iii. 如果需要長時間一直監控要用take,而如果只是在某個指定的時間監控則用poll;

4. 獲得WatchKey(監控池)中的具體監控資訊:

    1) 一個檔案變化動作可能會引發一系列的事件,因此WatchKey中儲存著一個事件列表List<WatchEvent<?>> list,可以通過WatchKey的pollEvents方法獲得該列表:

        i. 原型:List<WatchEvent<?>> WatchKey.pollEvents();

        ii. 獲得列表後既可以通過for迴圈迭代遍歷該變化動作所引發的所有具體事件(通常來說,大多數變化動作只會引發一個事件);

    2) WatchEvent<?>:

        i. 表示監控時間物件,裡面最常用的兩個方法就是context和kind;

        ii. Path WatchEvent<?>.context(); // 返回觸發該事件的那個檔案或目錄的路徑

        iii. Kind<StandardWatchEventKinds> WatchEvent<?>kind(); // 返回事件型別(ENTRY_CREATE、ENTRY_DELETE、ENTRY_MODIFY之一);

5. 完成一次監控就需要重置監控器一次:

    1) 直接呼叫WatchKey的reset方法就代表重置其關聯的監控器了;

    2) 原型:boolean WatchKey.reset();

    3) 因為當你使用poll或take時監控器執行緒就被阻塞了,因為你處理檔案變化的操作可能需要挺長時間的,為了防止在這段時間內又要處理其他類似的事件,因此需要阻塞監控器執行緒,而呼叫reset表示重啟該執行緒;

6. 示例:

public class Test {
	
	public static void main(String[] args) throws IOException, InterruptedException {
		WatchService watcher = FileSystems.getDefault().newWatchService();
		Paths.get("C:").register(watcher, 
				StandardWatchEventKinds.ENTRY_CREATE,
				StandardWatchEventKinds.ENTRY_DELETE,
				StandardWatchEventKinds.ENTRY_MODIFY);
		
		while (true) {
			WatchKey key = watcher.take();
			for (WatchEvent<?> event: key.pollEvents()) {
				System.out.println(event.context() + " comes to " + event.kind());
			}
			
			boolean valid = key.reset();
			if (!valid) {
				break;
			}
		}
	}
}

7. 監控範圍詳解:

    1) 例如以下檔案結構:

A(dir)  --- B(file)

           --- C(dir)  --- D(file)

                            --- E(dir)   ---  F...

A目錄中有兩個子檔案,其中B是檔案C是子目錄,C下又有兩個子檔案,D是檔案,E是子目錄,而E目錄下還有其它東西F等等;

    2) 現在監控A結點,那麼其監控範圍就只有A所有子節點,但不包括A子節點的子節點!

    3) 例如:你修改B,或者在A的目錄下建立/刪除檔案或者目錄都會被監控到;

    4) 當然修改C也會被監控到,那C是目錄,目錄怎麼修改呢?在作業系統中目錄的修改是這樣定義的,你在該目錄下新建、刪除、重新命名其直接子節點都算對該結點進行修改;

!!這其實很好理解,以為目錄其實在作業系統底層是一個簡單的文字檔案(你可以用Vim等編輯器開啟檢視),裡面記錄了該目錄下的所有子檔案(檔案/目錄)的名稱,設想,你對一個檔案進行修改其實就是更改檔案中的內容,那麼:

        a. 新建子節點:就是在該文字檔案中增加一條記錄,那當然算對該目錄進行修改咯!

        b. 刪除子節點:在該文字檔案中刪除一條記錄;

        c. 重新命名:改寫其中一條記錄;

!!因此這些都是赤裸裸地對目錄進行修改啊!!

    5) 小結:監控範圍就是該節點下所有子節點(檔案、目錄(目錄就是一個文字檔案))的所有建立、刪除、修改行為;