目錄:
Java NIO 學習筆記(一)----概述,Channel/Buffer
Java NIO 學習筆記(二)----彙集和分散,通道到通道
Java NIO 學習筆記(三)----Selector
Java NIO 學習筆記(四)----文件通道和網絡通道
Java NIO 學習筆記(五)----路徑、文件和管道 Path/Files/Pipe
Java NIO 學習筆記(六)----異步文件通道 AsynchronousFileChannel
Java NIO 學習筆記(七)----NIO/IO 的對比和總結html
Path 接口是 NIO2(AIO) 的一部分,是對 NIO 的更新,Path 接口已添加到 Java 7 中,徹底限定類名是 java.nio.file.Path 。java
Path 實例表示文件系統中的路徑。 路徑能夠指向文件或目錄,也能夠是絕對的或相對的。在某些操做系統中,不要將文件系統路徑與環境變量中的 path 路徑相混淆。 java.nio.file.Path 接口與路徑環境 path 變量無關。api
在許多方面,java.nio.file.Path 接口相似於 java.io.File 類,但存在一些細微差異。 但在許多狀況下,可使用 Path 接口替換 File 類的使用。網絡
可使用名爲 Paths.get() 的 Paths 類(java.nio.file.Paths)中的靜態方法建立 Path 實例,get()方法是 Path 實例的工廠方法,一個示例以下:oracle
public class PathExample { public static void main(String[] args) { // 使用絕對路徑建立 Path absolutePath = Paths.get("D:\\test\\1.txt"); // 使用相對路徑建立 Path relativePath = Paths.get("D:\\test", "1.txt"); System.out.println(absolutePath.equals(relativePath)); // ture } }
注意路徑分隔符在 Windows 上是「\」,在 Linux 上是 「/」。異步
Paths 類只有2個方法:ide
方法 | 描述 |
---|---|
static Path get(String first, String... more) | 將路徑字符串或在鏈接時造成路徑字符串的字符串序列轉換爲路徑。 |
static Path (URI uri) | 將給定URI轉換爲路徑對象。 |
Path 接口部分方法:函數
方法 | 描述 |
---|---|
boolean endsWith(Path other) | 測試此路徑是否以給定路徑結束。 |
boolean equals(Object other) | 取決於文件系統的實現。通常不區分大小寫,有時區分。 不訪問文件系統。 |
Path normalize() | 返回一個路徑,該路徑消除了冗餘的名稱元素,好比'.', '..' |
Path toAbsolutePath() | 返回表示該路徑的絕對路徑的路徑對象。 |
File toFile() | 返回表示此路徑的 File 對象。 |
String toString() | 返回的路徑字符串使用默認名稱分隔符分隔路徑中的名稱。 |
NIO 文件類(java.nio.file.Files)爲操做文件系統中的文件提供了幾種方法,File 類與 java.nio.file.Path 類一塊兒工做,須要瞭解 Path 類,而後才能使用 Files 類。post
static boolean exists(Path path, LinkOption... options)
options 參數用於指示,在文件是符號連接的狀況下,如何處理該符號連接,默認是處理符號連接的。其中 LinkOption 對象是一個枚舉類,定義如何處理符號連接的選項。整個類只有一個 NOFOLLOW_LINKS;
常亮,表明不跟隨符號連接。學習
Path output = Paths.get("D:\\test\\output"); Path newDir = Files.createDirectory(output); // Files.createDirectories(output); // 這個方法能夠一併建立不存在的父目錄 System.out.println(output == newDir); // true System.out.println(Files.exists(output)); // true
若是建立目錄成功,則返回指向新建立的路徑的 Path 實例,此實例和參數是同一個實例。
若是該目錄已存在,則拋出 FileAlreadyExistsException 。 若是出現其餘問題,可能會拋出IOException ,例如,若是所需的新目錄的父目錄不存在。
一共有 3 個複製方法:
static long copy(Path source, OutputStream out); static Path copy(Path source, Path target, CopyOption... options); static long copy(InputStream in, Path target, CopyOption... options)
其中 CopyOption 選項能夠選擇指定複製模式,通常是其子枚舉類 StandardCopyOption 提供選項,有 3 種模式,第二個參數是可變形參,能夠多個組合一塊兒使用:
ATOMIC_MOVE
:原子複製,不會被線程調度機制打斷的操做;一旦開始,就一直運行到結束;COPY_ATTRIBUTES
:同時複製屬性,默認是不復制屬性的;REPLACE_EXISTING
:重寫模式,會覆蓋已存在的目的文件;一個例子以下:
Path sourcePath = Paths.get("D:\\test\\source.txt"); // 源文件必須先存在 Path desPath = Paths.get("D:\\test\\des.txt"); // 目的文件能夠不存在 Files.copy(sourcePath, desPath); // 默認狀況,若是目的文件已存在則拋出異常 Files.copy(sourcePath, desPath, StandardCopyOption.REPLACE_EXISTING); // 覆蓋模式
注意:複製文件夾的時候,只能複製空文件夾,若是文件夾非空,須要遞歸複製,不然只能獲得一個空文件夾,而文件夾裏面的文件不會被複制。
只有 1 個移動文件或文件夾的方法:
static Path move(Path source, Path target, CopyOption... options);
若是文件是符號連接,則移動符號連接自己,而不是符號連接指向的實際文件。
和移動文件同樣,也存在第三個可選參數 CopyOption ,參考上述。若是移動文件失敗,可能會拋出 IOException,例如,若是文件已存在於目標路徑中,而且遺漏了覆蓋選項,或者要移動的源文件不存在等。
和複製文件夾不同,若是文件夾裏面有內容,複製只會複製空文件夾,而移動會把文件夾裏面的全部東西一塊兒移動過去,如下是一個移動文件夾的示例:
// 移動 s 目錄到一個不存在的新目錄 Path s = Paths.get("D:\\s"); Path d = Paths.get("D:\\test\\test"); Files.createDirectories(d.getParent()); Files.move(s, d);
和 Linux mv 命令同樣,重命名文件與移動文件方式相同,移動文件還能夠將文件移動到不一樣的目錄並能夠同時更改其名稱。 另外 java.io.File 類也可使用它的 renameTo() 方法來實現移動文件,但如今 java.nio.file.Files 類中也有文件移動功能。
static void delete(Path path); static boolean deleteIfExists(Path path); // 若是文件被此方法刪除則返回 true
若是文件是目錄,則該目錄必須爲空才能刪除。
刪除和複製文件夾的時候,若是文件夾爲空,那麼會刪除失敗或者只能複製空文件夾,此時可使用 walkFileTree() 方法進行遍歷文件樹,而後在 FileVisitor 對象的 visitFile() 方法中執行刪除或複製文件操做。
Files 類有 2 個重載的 walkFileTree() 方法,以下:
static Path walkFileTree(Path start, FileVisitor<? super Path> visitor); static Path walkFileTree(Path start, Set<FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor);
將 Path 實例和 FileVisitor 做爲參數,walkfiletree() 方法能夠遞歸遍歷目錄樹。Path 實例指向要遍歷的目錄。在遍歷期間調用 FileVisitor ,首先介紹 FileVisitor 接口:
public interface FileVisitor<T> { FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs) throws IOException; FileVisitResult visitFile(T file, BasicFileAttributes attrs) throws IOException; FileVisitResult visitFileFailed(T file, IOException exc) throws IOException; FileVisitResult postVisitDirectory(T dir, IOException exc) throws IOException; }
必須本身實現 FileVisitor 接口,並將其實現的實例傳遞給 walkFileTree() 方法。在目錄遍歷期間,將在不一樣的時間調用 FileVisitor 實現的 4 個方法,表明對遍歷到的文件或目錄進行什麼操做。若是不須要使用到全部方法,能夠擴展 SimpleFileVisitor 類,該類包含 FileVisitor 接口中全部方法的默認實現。
Files.walkFileTree(inputPath, new FileVisitor<Path>() { // 訪問文件夾以前調用此方法 @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { System.out.println("pre visit dir:" + dir); return FileVisitResult.CONTINUE; } // 訪問的每一個文件都會調用此方法,只針對文件,不會對目錄執行 @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { return FileVisitResult.CONTINUE; } // 訪問文件失敗會調用此方法,只針對文件,不會對目錄執行 @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } // 訪問文件夾以後會調用此方法 @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { return FileVisitResult.CONTINUE; } });
這四個方法都返回一個 FileVisitResult 枚舉實例。FileVisitResult 枚舉包含如下四個選項:
經過返回其中一個值,被調用的方法能夠決定文件遍歷時接下來應該作什麼。
walkFileTree() 方法還能夠用於搜索文件,下面這個例子擴展了 SimpleFileVisitor 來查找一個名爲 input.txt 的文件:
Path rootPath = Paths.get("D:\\test"); String fileToFind = File.separator + "input.txt"; Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { String fileString = file.toAbsolutePath().toString(); System.out.println("pathString: " + fileString); if (fileString.endsWith(fileToFind)) { System.out.println("file found at path: " + fileString); return FileVisitResult.TERMINATE; } return FileVisitResult.CONTINUE; } });
同理,刪除有內容的目錄時,能夠重寫 visitFile() 方法,並在裏面執行刪除文件操做,重寫 postVisitDirectory() 方法,並在裏面執行刪除目錄操做便可。
Files 類包含許多其餘有用的函數,例如用於建立符號連接,肯定文件大小,設置文件權限等的函數。有關java.nio.file.Files 類的詳細信息,請查看 JavaDoc
Pipe 是兩個線程之間的單向數據鏈接。管道有 source 通道和一個 sink 通道,將數據寫入 sink 通道,就能夠從 source 通道讀取該數據。
如下是管道原理的說明:
先看一個完整的例子:
public class PipeExample { public static void main(String[] args) throws IOException { Pipe pipe = Pipe.open(); Pipe.SinkChannel sinkChannel = pipe.sink(); // sink 通道寫入數據 String data = "some string"; ByteBuffer buffer = ByteBuffer.allocate(32); buffer.clear(); buffer.put(data.getBytes()); buffer.flip(); // 反轉緩衝區,準備被讀取 while (buffer.hasRemaining()) { sinkChannel.write(buffer); // 將 Buffer 的數據寫入 sink 通道 } Pipe.SourceChannel sourceChannel = pipe.source(); // 源通道讀取數據 ByteBuffer readBuffer = ByteBuffer.allocate(32); int bytesRead = sourceChannel.read(readBuffer); // 返回值表明讀取了多少數據 System.out.println("Read: " + bytesRead); // Read: 11 System.out.println(new String(readBuffer.array())); // some string } }
如上代碼,首先要建立管道,打開管道以後是使用同一個管道對象獲取對應的 sink 通道和 source 通道的,這會自動地將兩個通道鏈接起來,做爲對比,在標準 IO 管道中是分別建立讀管道和寫管道,而後在構造器中或者使用pipe1.connect(pipe2)
方法來鏈接起來,以下:
PipedOutputStream output = new PipedOutputStream(); PipedInputStream input = new PipedInputStream(); input.connect(output); // 或者使用以下1行代碼,能夠代替上面2行代碼來鏈接2個管道 //PipedInputStream input = new PipedInputStream(output);