Java NIO 學習筆記(五)----路徑、檔案和管道 Path/Files/Pipe
Path 介面和 Paths 類
Path 介面是 IO/">NIO2(AIO) 的一部分,是對 NIO 的更新,Path 介面已新增到 Java 7 中,完全限定類名是 java.nio.file.Path 。
Path 例項表示檔案系統中的路徑。 路徑可以指向檔案或目錄,路徑可以是絕對的或相對的。在某些作業系統中,不要將檔案系統路徑與環境變數中的 path 路徑相混淆。 java.nio.file.Path 介面與路徑環境 path 變數無關。
在許多方面,java.nio.file.Path 介面類似於 java.io.File 類,但存在一些細微差別。 但在許多情況下,可以使用 Path 介面替換 File 類的使用。
建立 Path 物件
可以使用名為 Paths.get() 的 Paths 類(java.nio.file.Paths)中的靜態方法建立 Path 例項,get()方法是 Path 例項的工廠方法,一個示例如下:
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個方法:
方法 | 描述 |
---|---|
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() | 返回的路徑字串使用預設名稱分隔符分隔路徑中的名稱。 |
Files
NIO 檔案類(java.nio.file.Files)為操作檔案系統中的檔案提供了幾種方法,File 類與 java.nio.file.Path 類一起工作,需要了解 Path 類,然後才能使用 Files 類。
判斷檔案是否存在
static boolean exists(Path path, LinkOption... options)
options 引數用於指示,在檔案是符號連結的情況下,如何處理該符號連結,預設是處理符號連結的。其中 LinkOption 物件是一個列舉類,定義如何處理符號連結的選項。整個類只有一個 NOFOLLOW_LINKS;
常亮,代表不跟隨符號連結。
createDirectory(Path path) 建立目錄
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
如果檔案是目錄,則該目錄必須為空才能刪除。
Files.walkFileTree() 靜態方法
刪除和複製資料夾的時候,如果資料夾為空,那麼會刪除失敗或者只能複製空資料夾,此時可以使用 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 列舉包含以下四個選項:
- CONTINUE : 繼續
- TERMINATE : 終止
- SKIP_SIBLINGS : 跳過兄弟節點,然後繼續
- SKIP_SUBTREE : 跳過子樹(不訪問此目錄的條目),然後繼續,僅在 preVisitDirectory 方法返回時才有意義,除此以外和 CONTINUE 相同。
通過返回其中一個值,被呼叫的方法可以決定檔案遍歷時接下來應該做什麼。
搜尋檔案
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 類中的其他方法
Files 類包含許多其他有用的函式,例如用於建立符號連結,確定檔案大小,設定檔案許可權等的函式。有關java.nio.file.Files 類的詳細資訊,請檢視 ofollow,noindex">JavaDoc
管道 Pipe
Pipe 是兩個執行緒之間的單向資料連線。管道有 source 通道和一個 sink 通道,將資料寫入 sink 通道,就可以從 source 通道讀取該資料。
以下是管道原理的說明:

image
使用管道進行讀取資料
先看一個完整的例子:
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);