檔案系統的 I/O,除了前幾篇所說明的檔案相關處理外,另一個主題就是目錄的處理 - 讀取、走訪、過濾檔案等,說明如下:
- 取得根目錄
1 package idv.steven.nio2.filedir; 2 3 import static java.lang.System.out; 4 import java.nio.file.FileSystems; 5 import java.nio.file.Path; 6 import java.util.ArrayList; 7 8 public class ListRoots { 9 10 public static void main(String[] args) { 11 Iterable<Path> dirs = FileSystems.getDefault().getRootDirectories(); 12 13 ArrayList<Path> list = new ArrayList<Path>(); 14 for(Path name : dirs) { 15 list.add(name); 16 } 17 Path[] array = new Path[list.size()]; 18 list.toArray(array); 19 20 for(Path path : array) { 21 System.out.println(path); 22 } 23 24 //dirs.forEach(out::println); //Java 8 25 } 26 }
如上的程式,第 11 行可以取得檔案系統的根目錄,在單根檔案系統,例如 Unix/Linux,當然就只會輸出 /,在 Windows 上執行,就看作業系統裡有幾個磁碟,像是我的電腦,執行出來就會是 .....
C:\
D:\
E:\
F:\
H:\
程式 13~22 行是傳統 Java 的寫法,使用 Lambda 的話,只要第 24 行這樣一行程式就可以了。
- 檢查檔案或目錄是否存在?
1 package idv.steven.nio2.filedir; 2 3 import java.nio.file.FileSystems; 4 import java.nio.file.Files; 5 import java.nio.file.LinkOption; 6 import java.nio.file.Path; 7 8 public class CheckExist { 9 public static void main(String[] args) { 10 Path path = FileSystems.getDefault().getPath("D:/TEST"); 11 boolean isNotExist = Files.notExists(path, new LinkOption[] {LinkOption.NOFOLLOW_LINKS}); 12 System.out.println(isNotExist); 13 } 14 }
不管是檢查檔案或目錄是否存在,使用的方法是一樣的,上面的程式是檢查 D:/ 下是否有 TEST 這個子目錄,注意看一下第 11 行,這裡使用的是 notExists 這個 method,Files 類別也提供有 exists method,notExists 就等於 !exists。
- 讀取目錄
1 package idv.steven.nio2.filedir; 2 3 import java.io.IOException; 4 import java.nio.file.DirectoryStream; 5 import java.nio.file.Files; 6 import java.nio.file.Path; 7 import java.nio.file.Paths; 8 9 public class ListDirectory { 10 11 public static void main(String[] args) { 12 Path path = Paths.get("/home/steven/TEST"); 13 try (DirectoryStream<Path> ds = Files.newDirectoryStream(path, "[rR]eadme*.txt")) { 14 for(Path file : ds) { 15 System.out.println(file.getFileName()); 16 } 17 } 18 catch (IOException e) { 19 System.err.println(e); 20 } 21 } 22 }
如上是 java.nio 提供的讀取目錄內容的方法之一,第二個參數不填,則會列出目錄裡的所有檔案、子目錄、連結等所有內容。第二個參數有那些選項呢? 如下:
- *: 比對所有字元。
- **: 跨目錄比對所有字元。
- ?: 比對一個字元,如果用這個選項,例如: a?.txt,那麼就一定是 a 後面要接一個字元,a.txt 不算在這個條件裡。
- { }: 比對大括號內所有 pattern,例如: {a?.txt, *.jpg},那麼可能就會列出 ab.txt、face.jpg、leg.jpg …
- [ ]: 比對中括號內所有列出的字元,像是上面的程式,會找出 readme*.txt 及 Readme*.txt 的檔案,中括號內,可以放入如下選項:
(1) [0-9]: 0 到 9 的所有數字,當然,也可以是[3-7],只搜尋 3 到 7 的數字。
(2) [A-Z]: 大寫 A 到 Z。
(3) [a-z,A-Z]: 大小寫的所有英文字母。
(4) [2345RT]: 比對到括號中任一字元。
6. 大括號、中括號內也可以有 *、? 等萬用字元,並複合使用,例如: {*[0-9].jpg},輸出的結果可能就會是 face1.jpg、leg3.jpg …
- 走訪目錄
上面的說明有提到讀取目錄的方法,如果我們要走訪目錄呢? java.nio 提到了一個介面 FileVisitor,實作這個介面後,會有四個 callback method,在走訪時,java.nio 會在各個時間點呼叫這四個 method,讓我們可以進行各種處理,四個介面說明如下:
- preVisitDirectory: 走訪一個目錄前。
- postVisitDirectory: 走訪一個目錄之後。
- visitFile: 走訪一個檔案時。
- visitFileFailed: 走訪檔案失敗,通常是因為無存取權限。
接下來看一下程式:
1 package idv.steven.nio2.filedir; 2 3 import java.io.IOException; 4 import java.nio.file.FileVisitResult; 5 import java.nio.file.FileVisitor; 6 import java.nio.file.Files; 7 import java.nio.file.Path; 8 import java.nio.file.Paths; 9 import java.nio.file.attribute.BasicFileAttributes; 10 import static java.nio.file.FileVisitResult.*; 11 12 public class WalkFolder implements FileVisitor<Path> { 13 14 @Override 15 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) 16 throws IOException { 17 18 System.out.println("preVisitDirectory: " + dir.getFileName()); 19 20 return CONTINUE; 21 } 22 23 @Override 24 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 25 throws IOException { 26 27 System.out.println("visitFile: " + file.getFileName()); 28 29 return CONTINUE; 30 } 31 32 @Override 33 public FileVisitResult visitFileFailed(Path file, IOException exc) 34 throws IOException { 35 36 System.out.println("visitFileFailed: " + file.getFileName()); 37 38 return CONTINUE; 39 } 40 41 @Override 42 public FileVisitResult postVisitDirectory(Path dir, IOException exc) 43 throws IOException { 44 45 System.out.println("postVisitDirectory: " + dir.getFileName()); 46 47 return CONTINUE; 48 } 49 50 public static void main(String[] args) throws IOException { 51 WalkFolder walkFolder = new WalkFolder(); 52 53 Files.walkFileTree(Paths.get("D:/TEST"), walkFolder); 54 } 55 }
這個程式會走訪 D:/TEST 下的所有子目錄及檔案,在 method 中可進行需要的處理,特別要提出來說明的是返回值,上面四個 method 都返回 CONTINE,表示繼續走訪,java.nio 提供了四個返回值,如下:
- CONTINUE: 繼續走訪
- SKIP_SIBLINGS: 跳過同一階層的檔案或子目錄後繼續走訪
- SKIP_SUBTREE: 跳過正走訪的這個目錄後繼續走訪
- TERMINATE: 停止走訪
在許多的情況下,我們並不會需要同時實作四個 method,所以 java.nio 提供了一個 SimpleFileVisitor 類別,繼承這個類別,我們只需要 override 要特別處理的 method,這樣會方便些。
- 拷貝、搬移、刪除
1 package idv.steven.nio2.action; 2 3 import java.io.IOException; 4 import java.nio.file.Files; 5 import java.nio.file.Path; 6 import java.nio.file.Paths; 7 8 public class Copy { 9 10 public static void main(String[] args) throws IOException { 11 Path from = Paths.get("D:/TEST/readme.txt"); 12 Path to = Paths.get("D:/readme.txt"); 13 Files.copy(from, to); 14 } 15 }
上面的程式,很簡單的都用預設值,將檔案由 from 拷貝一份到 to,Files.copy(...) 是可以帶參數,以指出要如何拷貝檔案,例如將它改寫如下:
import static java.nio.file.StandardCopyOption.*; import static java.nio.file.LinkOption.*; … Files.copy(from, to, REPLACE_EXISTING, COPY_ATTRIBUTES, NOFOLLOW_LINKS);
第三個參數指出檔案的屬性也要拷貝到新檔,第四個參數則指出,如果這個檔案是 symbol link 就不要拷貝,這些參數定義在 StandardCopyOption 及 LinkOption 套件中。同樣的程式,可以用來拷貝目錄嗎? 可以,不過,目錄下的檔案不會被拷貝。Files 這個類別還提供有 move、delete 等 method,可以用來搬移、刪除檔案、目錄,詳細可以參考 Java Doc。
沒有留言:
張貼留言