Java NIO 學習筆記(五)----路徑、檔案和管道 Path/Files/Pipe
目錄:
Java NIO 學習筆記(一)----概述,Channel/Buffer
Java NIO 學習筆記(二)----聚集和分散,通道到通道
Java NIO 學習筆記(三)----Selector
Java NIO 學習筆記(四)----檔案通道和網路通道
Java NIO 學習筆記(五)----路徑、檔案和管道 Path/Files/Pipe
Java NIO 學習筆記(六)----非同步檔案通道 AsynchronousFileChannel
Path 介面和 Paths 類
Path 介面是 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 類的詳細資訊,請檢視 JavaDoc
管道 Pipe
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);