Java:前程似錦的 NIO 2.0

Java 之因此可以霸佔編程語言的榜首,其強大、豐富的類庫功不可沒,幾乎全部的編程問題都能在其中找到解決方案。但在早期的版本當中,輸入輸出(I/O)流並不那麼令開發者感到愉快:css


1)JDK 1.4 以前的 I/O 沒有緩衝區的概念、不支持正則表達式、支持的字符集編碼有限等等;java

2)JDK 1.4 的時候引入了非阻塞 I/O,也就是 NIO 1.0,但遍歷目錄很困難,不支持文件系統的非阻塞操做等等。nginx


爲了突破這些限制,JDK 1.7 的時候引入了新的 NIO,也就是本篇文章的主角——NIO 2.0。git


0一、基石:Path程序員


Path 既能夠表示一個目錄,也能夠表示一個文件,就像 File 那樣——固然了,Path 就是用來取代 File 的。web


1)能夠經過 Paths.get() 建立一個 Path 對象,此時 Path 並無真正在物理磁盤上建立;參數既能夠是一個文件名,也能夠是一個目錄名;絕對路徑或者相對路徑都可。面試


2)能夠經過 Files.notExists() 確認 Path(目錄或者文件) 是否已經存在。正則表達式


3)能夠經過 Files.createDirectory() 建立目錄,此時目錄已經在物理磁盤上建立成功,可經過資源管理器查看到。算法


4)能夠經過 Files.createFile() 建立文件,此時文件已經在物理磁盤上建立成功,可經過資源管理器查看到。編程


5)能夠經過 toAbsolutePath() 查看 Path 的絕對路徑。


6)能夠經過 resolve() 將 Path 鏈接起來,參數能夠是一個新的 Path 對象,也能夠是對應的字符串。


具體的代碼以下:


public class Wanger {
public static void main(String[] args) { // 相對路徑 Path dir = Paths.get("chenmo");
// 輸出 dir 的絕對路徑 System.out.println(dir.toAbsolutePath()); // 輸出:D:\program\java.git\java_demo\chenmo
if (Files.notExists(dir)) { try { // 若是目錄不存在,則建立目錄 Files.createDirectory(dir); } catch (IOException e1) { e1.printStackTrace(); } } // 這時候 chenmo.txt 文件並未建立 // 經過 resolve 方法把 dir 和 chenmo.txt 連接起來 Path file = dir.resolve("chenmo.txt");
// 輸出 file 的絕對路徑 System.out.println(file.toAbsolutePath()); // 輸出:D:\program\java.git\java_demo\chenmo\chenmo.txt
if (Files.notExists(file)) { try { // 若是文件不存在,則建立文件 Files.createFile(file); } catch (IOException e) { e.printStackTrace(); } }
}
}


若是要將 File 轉換爲 Path,能夠經過 File 類的 toPath() 方法完成。代碼示例以下:


File file = new File("沉默王二.txt");Path path = file.toPath();


若是要將 Path 轉換爲 File,能夠經過 Path 類的 toFile() 方法完成。代碼示例以下:


Path path = Paths.get("沉默王二.txt");File file = path.toFile();


0二、處理目錄


NIO 2.0 新增的 java.nio.file.DirectoryStream<T> 接口能夠很是方便地查找目錄中的(符合某種規則的)文件,好比說咱們要查找 chenmo 目錄下的 txt 後綴的文件,代碼示例以下:


// 相對路徑Path dir = Paths.get("chenmo");
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.txt")) { for (Path entry : stream) { System.out.println(entry.getFileName()); }} catch (IOException e) { e.printStackTrace();}


1)Files.newDirectoryStream(Path dir, String glob) 會返回一個過濾後的 DirectoryStream( 目錄流,),第一個參數爲目錄,第二個參數爲 glob 表達式,好比 *.txt 表示全部 txt 後綴的文件。


2)因爲 DirectoryStream 繼承了 Closeable 接口,因此它能夠配合 try-with-resources 語法寫出更安全的代碼,目錄流會自動調用 close 方法關閉流,釋放與流相關的資源,不須要再經過 finally 進行主動關閉。


3)DirectoryStream 被稱爲目錄流,容許方便地使用 for-each 結構來遍歷目錄。


0三、處理目錄樹


目錄樹意味着一個目錄裏既有文件也有子目錄,也可能都沒有,也可能有其一。NIO 2.0 能夠很方便地遍歷一顆目錄樹,並操做符合條件的文件;這其中關鍵的一個方法就是 Files 類的 walkFileTree,其定義以下:


public static Path walkFileTree(Path start, FileVisitor<? super Path> visitor) throws IOException{ return walkFileTree(start, EnumSet.noneOf(FileVisitOption.class), Integer.MAX_VALUE, visitor); }


第二個參數 FileVisitor 被稱爲文件訪問器接口,它實現起來很是複雜,要實現 5 個方法呢,但幸虧 JDK 的設計者提供了一個默認的實現類 SimpleFileVisitor,若是咱們只想從目錄樹中找到 txt 後綴的文件,能夠這樣作:


// 相對路徑Path dir = Paths.get("chenmo");
try { Files.walkFileTree(dir, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { if (file.toString().endsWith(".txt")) { System.out.println(file.getFileName()); } return FileVisitResult.CONTINUE; } });} catch (IOException e) { e.printStackTrace();}


經過建立匿名內部類來重寫 SimpleFileVisitor 的 visitFile 方法,若是後綴名爲 txt 就打印出來。


0四、文件的刪除、複製、移動


建立一個文件很是的簡單,以前咱們已經體驗過了,那麼刪除一個文件也一樣的簡單,代碼示例以下:


Files.delete(file);Files.deleteIfExists(file);


使用 Files.delete() 刪除文件以前最好使用 Files.exists() 判斷文件是否存在,不然會拋出 NoSuchFileException;Files.deleteIfExists() 則不用。


複製文件也不復雜,代碼示例以下:


Path source = Paths.get("沉默王二.txt");Path target = Paths.get("沉默王二1.txt");Files.copy(source, target);


移動文件和複製文件很是類似,代碼示例以下:


Path source = Paths.get("沉默王二.txt");Path target = Paths.get("沉默王二1.txt");Files.move(source, target);


0五、快速地讀寫文件


NIO 2.0 提供了帶有緩衝區的讀寫輔助方法,使用起來也很是的簡單。能夠經過 Files.newBufferedWriter() 獲取一個文件緩衝輸入流,並經過 write() 方法寫入數據;而後經過 Files.newBufferedReader() 獲取一個文件緩衝輸出流,經過 readLine() 方法讀出數據。代碼示例以下。


Path file = Paths.get("沉默王二.txt");
try (BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) { writer.write("一個有趣的程序員");} catch (Exception e) { e.printStackTrace();}
try (BufferedReader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
String line; while ((line = reader.readLine()) != null) { System.out.println(line); }} catch (Exception e) { e.printStackTrace();}


 0六、重要:異步 I/O 操做


實話實說吧,上面提到的那些都算是 NIO 2.0 的甜點,而異步 I/O 操做(也稱 AIO)纔算是真正重要的內容。異步 I/O 操做能夠充分利用多核 CPU 的特色,不須要再像之前那樣啓動一個線程來對 I/O 進行處理,省得阻塞了主線程的其餘操做。


異步 I/O 操做的核心概念是發起非阻塞方式的 I/O 操做,當 I/O 操做完成時通知。能夠分爲兩種形式:Future 和 Callback。若是咱們但願主線程發起 I/O 操做並輪循等待結果時,通常使用 Future 的形式;而 Callback 的基本思想是主線程派出一個偵查員(CompletionHandler)到獨立的線程中執行 I/O 操做,操做完成後,會觸發偵查員的 completed 或者 failed 方法。


1)Future 


先來看一個示例,代碼以下:


public static void main(String[] args) throws IOException, InterruptedException, ExecutionException { Path file = Paths.get("沉默王二.txt"); AsynchronousFileChannel channel = AsynchronousFileChannel.open(file); Future<Integer> result = channel.read(ByteBuffer.allocate(100_000), 0); while (!result.isDone()) { System.out.println("主線程繼續作事情"); }
Integer bytesRead = result.get(); System.out.println(bytesRead);}



1)經過 AsynchronousFileChannel.open() 打開一個異步文件通道 channel。


2)用 Future 來保存從通道中讀取的結果。


3)經過 isDone() 輪循判斷異步 I/O 操做是否完成,若是沒有完成的話,主線程能夠繼續作本身的事情。


2)Callback


先來看一個示例,代碼以下:


public static void main(String[] args) throws IOException, InterruptedException, ExecutionException { Path file = Paths.get("沉默王二.txt"); AsynchronousFileChannel channel = AsynchronousFileChannel.open(file); channel.read(ByteBuffer.allocate(100_000), 0, null, new CompletionHandler<Integer, ByteBuffer>() { public void completed(Integer result, ByteBuffer attachment) { System.out.println(result); }  public void failed(Throwable exc, ByteBuffer attachment) { System.out.println(exc.getMessage()); } });  System.out.println("主線程繼續作事情");
}



1)經過 AsynchronousFileChannel.open() 打開一個異步文件通道 channel。


2)在 read 方法中使用匿名內部類的形式啓用 CompletionHandler,而後實現 CompletionHandler 的兩個監聽方法,completed 的時候打印結果,failed 的時候打印異常信息。


不論是 Future 形式仍是 Callback 形式,總之異步 I/O 是一個強大的特性,能夠保證在處理大文件時性能不受到顯著的影響。



往期精彩回顧:

JVM虛擬機系列

JVM內存結構和垃圾回收算法(一)

內存結構和垃圾回收算法(二)

老年代的垃圾回收算法(三)

於加入知識星球的同窗提供基本的福利:

文章有疑問的地方能夠提問,其餘工做問題均可以提問出來,做者免費做答。

 https://t.zsxq.com/Y3fYny7


每週都有大牛分享一些面試題,和麪試注意的知識點!

 https://t.zsxq.com/2bufE2v


每週由Java極客技術獨家編制的設計模式與你們分享!

 https://t.zsxq.com/3bUNbEI


每兩週還會分享一個話題,和你們一塊兒成長!

 https://t.zsxq.com/BI6Unm2


還有Java極客技術團隊親自錄製了一套 Spring Boot 視頻,這套視頻加密,加密後放到雲盤上,下載連接加密以後,一機一碼,每一個星球的用戶一個播放受權碼。

 

咱們作知識星球的目的和其餘星主同樣,就是爲了幫助你們一塊兒更好的成長,與高手拉近距離,減小差距,其實你也是高手!

1000人,50元/每一年,如今大約還剩487名額。

長按二維碼

隆重介紹:

Java 極客技術公衆號,是由一羣熱愛 Java 開發的技術人組建成立,專一分享原創、高質量的 Java 文章。若是您以爲咱們的文章還不錯,請幫忙讚揚、在看、轉發支持,鼓勵咱們分享出更好的文章。


本文分享自微信公衆號 - Java極客技術(Javageektech)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索